Leetcode:Merge k Sorted Lists
題目大意是傳入一個鏈表數組lists,每個鏈表都由若幹個鏈接的鏈表結點組成,並且每個鏈表結點記錄一個整數。題目保證傳入的鏈表中的整數按從小到大進行排序。
題目要求我們輸出一個新的鏈表,這個鏈表中應該包含所有在lists中出現的整數,並且按從小到大排序。
我的思路:
這個問題解法應該很多。我采用的解法是分治算法,靈感來自於歸並排序,因為歸並排序的遞歸完成後也會涉及到將兩個有序數組重組合並。
首先說明如何重組兩個鏈表為一個有序鏈表,其思路源自於我們在玩撲克牌時對兩堆有序撲克牌進行排序的手法:
1.我們從兩疊有序撲克牌(正面向上)中,選取最小的牌面(即兩疊撲克牌蓋在最上面的兩張牌中較小的牌),並加入到手牌中。
2.循環上一步直到有一疊牌被完全取走。
3.將剩下的一疊牌全部按序加入到手牌中。
由於始終選取較小的牌,以及兩疊牌的從小到大排序的性質,能保證剩余的牌總是會不小於被選走的手牌。假設兩疊牌共n張,由於這個過程中每次都會使得桌上的牌減少1,而減少到0時就會必定結束,因此上面的步驟最多只會執行n次,這n次中每一次都涉及一次比較和取牌(在計算機中是常量時間),因此我們可以認為要合並兩個有序牌堆的時間復雜度為O(n)。
combine(A, B):
ai = 0, bi = 0
al = A.length, bl = B.length
result = empty-list
while(ai < al && bi < bl)
if(A[ai] <= B[bi])
insert A[ai] into result
ai = ai + 1
else
insert B[bi] into result
bi = bi + 1
while(ai < al)
insert A[ai] into result
ai = ai + 1
while(bi < bl)
insert B[bi] into result
bi = bi + 1
return result
但是題目已經指出了,k:=lists.length不一定為2。因此我們要通過分治算法將需要合並的鏈表數減少為2。其思路是先合並下標為0~k/2的鏈表,在合並k/2~k的鏈表。最後利用我們上面所說的洗牌算法將兩個鏈表合並。
rec(lists, from, to) //合並lists[from], ... lists[to - 1]為一個鏈表,並返回合並的鏈表
if(from + 1 == to)
return lists[i]
half = (from + to) / 2
part1 = rec(lists, from, half)
part2 = rec(lists, half, to)
return combine(part1, part2)
上面就是我們歸並算法的完整實現。先說明它能返回正確的結果。當to - from == 1時,此時需要排序的鏈表只有一個,只要直接返回這個鏈表就可以了。因此當to - from == 1時這個算法是正確的。假設當to-from<p時(p >1),這個算法能返回正確的結果。那麽當to-from=p時,函數將排序lists中from~half段和half~to段的鏈表委托給遞歸函數。由於from < half < to,因此half-from<to-from=p,to-half<to-from=p,因此遞歸函數對於部分的排序返回了正確的結果,分別記為part1,part2,part1中保存了from~half段的合並結果,而part2中保存了half~to段的合並結果。最終利用我們已經說明過的洗牌合並法對合並part1和part2,因此最終得到的結果則是lists[from], ... lists[to - 1]的有序合並結果。這裏用了一個關鍵的想法,即合並順序不會改變最終結果。利用數學歸納法我們可以得出對於任意to-from>=1,rec函數都能返回正確的結果。
接下來說明這個算法的復雜度。
首先空間復雜度應該是O(n),因為函數遞歸發生在空間分配之前,因此當函數從下級遞歸中跳出時,最多持有與lists中保存的整數的拷貝(保存在part1和part2中),在combine函數中又會分配一次空間,但也最多只是part1和part2的長度的加總,因此,rec函數同時占據的空間量不會超過2n=O(n)。再提一下,這裏由於沒有要求我們不能使用傳入的鏈表,因此假如我們直接通過操作鏈表進行排序而不分配額外的空間,那麽空間復雜度就可以被優化到O(1)。
時間復雜度我們必須展開來看。一種簡明的方式是將不同的遞歸深度區分開來 ,在同一遞歸深度中的時間復雜度為O(n)。比方說在第一級遞歸,我們對長度為k的lists進行合並,但是實際上發生在這一遞歸深度的只有調用combine的合並操作,其時間復雜度為O(n)。同樣在第二級遞歸,我們分別對0~k/2和k/2~k進行排序,而發生在這一遞歸深度的實際的操作只有combine操作,兩個combine操作正好涉及了lists[0~k]中所有的元素,因此時間復雜度也是O(n)。考慮到遞歸深度最大為log2(k),因此總的時間復雜度為O(n)*log2(k)=O(nlog2(k))。
最後提供一下java代碼,13ms AC:
1 /** 2 * Definition for singly-linked list. 3 * public class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { val = x; } 7 * } 8 */ 9 public class Solution { 10 11 public ListNode mergeKLists(ListNode[] lists) { 12 if (lists.length <= 1) { 13 return lists.length == 0 ? null : lists[0]; 14 } 15 return mergeKLists(lists, 0, lists.length); 16 } 17 18 public ListNode mergeKLists(ListNode[] lists, int from, int to) { 19 if (to == from + 1) { 20 return lists[from]; 21 } 22 int half = (from + to) / 2; 23 ListNode part1 = mergeKLists(lists, from, half); 24 ListNode part2 = mergeKLists(lists, half, to); 25 if (part1 == null || part2 == null) { 26 return part1 == null ? part2 : part1; 27 } 28 if (part1.val > part2.val) { 29 ListNode tmp = part1; 30 part1 = part2; 31 part2 = tmp; 32 } 33 ListNode traceNode1 = part1; 34 ListNode traceNode2 = part2; 35 ListNode formerNode1 = null; 36 ListNode formerNode2 = null; 37 while (traceNode1 != null && traceNode2 != null) { 38 39 while (traceNode1 != null && traceNode2 != null && traceNode1.val <= traceNode2.val) { 40 formerNode1 = traceNode1; 41 traceNode1 = traceNode1.next; 42 } 43 ListNode start = traceNode2; 44 while (traceNode1 != null && traceNode2 != null && traceNode2.val <= traceNode1.val) { 45 formerNode2 = traceNode2; 46 traceNode2 = traceNode2.next; 47 } 48 if (formerNode1 != null) { 49 formerNode1.next = start; 50 formerNode1 = formerNode2; 51 } 52 if (formerNode2 != null) { 53 formerNode2.next = traceNode1; 54 formerNode2 = null; 55 } 56 } 57 return part1; 58 } 59 }View Code
Leetcode:Merge k Sorted Lists