問題描述
這是從微博上看到的一個面試問題,描述如下:
給一個鏈表,如下定義:
1 struct Node 2 { 3 struct Node* next; 4 struct Node* random; 5 void*data; 6 };
其中random 指向鏈表中的任意一個節點或為空。
現在要求對一個這種鏈表進行深度復制(即復制得到的鏈表中節點的next, random指向新鏈表中的相對應位置)。
如下圖, 第一個是原鏈表,第二個是復制后的鏈表,現在要求盡可能快、省地完成這個復制過程。
簡單分析
這個問題的難點顯然就在於怎么設置新鏈表節點的random指針。
一種比較直觀的解法是類似於深度優先進行復制:
1) 復制節點A1,得到B1
2) 復制節點A1的random指向的節點NA1.
3) A1 = A1->next。
4) 如果A1不為空,重復以上動作。
但是上面的做法有問題,因為random是隨意指向鏈表中的節點,復制random與復制next的過程有重復,而上面的做法卻沒有檢查哪些節點是已經復制過的了。
為解決這個問題,我們可以在復制的過程中引入一個hash,用來保存哪些節點已經被復制過了。
1) 檢查hash,如果A1不在表中,對A1進行復制,得到B1, 並把A1放進hash表。
2) 檢查A1的random指向的節點NA1是否在hash中,如果NA1不在表中,對NA1進行復制,到NB1, 並把NA1放入hash表中。
3) A1 = A1->next;
4) 如果A1不為空,重復以上動作。
以上做法如果忽視檢查hash的時間消耗的話,時間復雜度為O(n), 但是空間復雜度也是O(n).
這個解決也可以接受了,也是比較容易想到的。
巧解
但事實還有一個比較巧的做法,細想一下,設置random的關鍵是新鏈表節點與舊鏈表節點之間要能建立起對應的關系。
這個關系應該怎樣建呢? 請看下圖
如圖,藍色節點是舊鏈表中的節點,紫色節點是新復制的節點,紅色線條是舊節點中的next,綠色線條是新節點中的next,藍色線條是新節點的random,黑色線條是舊節點的random。
算法如下 :
1) 掃描舊鏈表,逐個節點復制,得到上圖中的長鏈表, 時間空間復雜度均為O(n)
2) 注意到上一步建立的新鏈表中,每個舊節點都指向了相對應的新節點。
剩下的問題就是怎么設置新節點的rando,現在開始設置新節點的random,及設置好新舊節點的next.
令A = A1;
a) 找到舊節點A的random指向的NA
b) B=A->next, B1是A1對應的新節點
c) NB = NA->next.
d) A->next = B->next; B->random = NB; B->next = A->next->next;
e) A = A->next
f) 如果A不為空,重復以上動作。
這個解法比較巧妙,它的優點是時間復雜度為O(n), 且空間復雜度為O(1).
不好的地方嘛--這個解法需要修改原來的鏈表。