題目:有一個複雜鏈表,其結點除了有一個m_pNext指標指向下一個結點外,還有一個m_pSibling指向鏈表中的任一結點或者NULL。其結點的C++定義如下:
struct ComplexNode
{
int m_nValue;
ComplexNode* m_pNext;
ComplexNode* m_pSibling;
};
下圖是一個含有5個結點的該類型複雜鏈表。圖中實線箭頭表示m_pNext指標,虛線箭頭表示m_pSibling指標。為簡單起見,指向NULL的指標沒有畫出。
請完成函數 ComplexNode* Clone(ComplexNode* pHead),以複製一個複雜鏈表。
分析:在常見的資料結構上稍加變化,這是一種很新穎的面試題。要在不到一個小時的時間裡解決這種類型的題目,我們需要較快的反應能力,對資料結構透徹的理解以及紮實的編程功底。
看到這個問題,我的第一反應是分成兩步:第一步是複製原始鏈表上的每個鏈表,並用m_pNext連結起來。第二步,假設原始鏈表中的某節點N的m_pSibling指向結點S。由於S的位置在鏈表上有可能在N的前面也可能在N的後面,所以要定位N的位置我們需要從原始鏈表的頭結點開始找。假設從原始鏈表的頭結點開始經過s步找到結點S。那麼在複製鏈表上結點N的m_pSibling的S’,離複製鏈表的頭結點的距離也是s。用這種辦法我們就能為複製鏈表上的每個結點設定m_pSibling了。
對一個含有n個結點的鏈表,由於定位每個結點的m_pSibling,都需要從鏈表頭結點開始經過O(n)步才能找到,因此這種方法的總時間複雜度是O(n2)。
由於上述方法的時間主要花費在定位結點的m_pSibling上面,我們試著在這方面去做最佳化。我們還是分為兩步:第一步仍然是複製原始鏈表上的每個結點N,並建立N’,然後把這些建立出來的結點連結起來。這裡我們對<N,N’>的配對資訊放到一個雜湊表中。第二步還是設定複製鏈表上每個結點的m_pSibling。如果在原始鏈表中結點N的m_pSibling指向結點S,那麼在複製鏈表中,對應的N’應該指向S’。由於有了雜湊表,我們可以用O(1)的時間根據S找到S’。
第二種方法相當於用空間換時間,以O(n)的空間消耗實現了O(n)的時間效率。
接著我們來換一種思路,在不用輔助空間的情況下實現O(n)的時間效率。第三種方法的第一步仍然是根據原始鏈表的每個結點N,建立對應的N’。這一次,我們把N’連結在N的後面。執行個體中的鏈表經過這一步之後變成了:
這一步的代碼如下:
/////////////////////////////////////////////////////////////////////////////////// Clone all nodes in a complex linked list with head pHead,// and connect all nodes with m_pNext link/////////////////////////////////////////////////////////////////////////////////void CloneNodes(ComplexNode* pHead){ ComplexNode* pNode = pHead; while(pNode != NULL) { ComplexNode* pCloned = new ComplexNode(); pCloned->m_nValue = pNode->m_nValue; pCloned->m_pNext = pNode->m_pNext; pCloned->m_pSibling = NULL; pNode->m_pNext = pCloned; pNode = pCloned->m_pNext; }}
第二步是設定我們複製出來的鏈表上的結點的m_pSibling。假設原始鏈表上的N的m_pSibling指向結點S,那麼其對應複製出來的N’是N->m_pNext,同樣S’也是S->m_pNext。這就是我們在上一步中把每個結點複製出來的結點連結在原始結點後面的原因。有了這樣的連結方式,我們就能在O(1)中就能找到每個結點的m_pSibling了。例子中的鏈表經過這一步,就變成如下結構了:
這一步的代碼如下:
/////////////////////////////////////////////////////////////////////////////////// Connect sibling nodes in a complex link list/////////////////////////////////////////////////////////////////////////////////void ConnectSiblingNodes(ComplexNode* pHead){ ComplexNode* pNode = pHead; while(pNode != NULL) { ComplexNode* pCloned = pNode->m_pNext; if(pNode->m_pSibling != NULL) { pCloned->m_pSibling = pNode->m_pSibling->m_pNext; } pNode = pCloned->m_pNext; }}
第三步是把這個長鏈表拆分成兩個:把奇數位置的結點連結起來就是原始鏈表,把偶數位置的結點連結出來就是複製出來的鏈表。上述例子中的鏈表拆分之後的兩個鏈表如下:
要實現這一步,也不是很難的事情。其對應的代碼如下:
/////////////////////////////////////////////////////////////////////////////////// Split a complex list into two:// Reconnect nodes to get the original list, and its cloned list/////////////////////////////////////////////////////////////////////////////////ComplexNode* ReconnectNodes(ComplexNode* pHead){ ComplexNode* pNode = pHead; ComplexNode* pClonedHead = NULL; ComplexNode* pClonedNode = NULL; if(pNode != NULL) { pClonedHead = pClonedNode = pNode->m_pNext; pNode->m_pNext = pClonedNode->m_pNext; pNode = pNode->m_pNext; } while(pNode != NULL) { pClonedNode->m_pNext = pNode->m_pNext; pClonedNode = pClonedNode->m_pNext; pNode->m_pNext = pClonedNode->m_pNext; pNode = pNode->m_pNext; } return pClonedHead;}
我們把上面三步合起來,就是複製鏈表的完整過程:
/////////////////////////////////////////////////////////////////////////////////// Clone a complex linked list with head pHead/////////////////////////////////////////////////////////////////////////////////ComplexNode* Clone(ComplexNode* pHead){ CloneNodes(pHead); ConnectSiblingNodes(pHead); return ReconnectNodes(pHead);}
本文已經收錄到《劍指Offer——名企面試官精講典型編程題》一書中,有改動,書中的分析講解更加詳細。歡迎關注。
博主何海濤對本部落格文章享有著作權。網路轉載請註明出處http://zhedahht.blog.163.com/。整理出版物請和作者聯絡。對解題思路有任何建議,歡迎在評論中告知,或者加我微博http://weibo.com/zhedahht或者http://t.163.com/zhedahht與我討論。謝謝。