Leetcode Week3 Merge Two(k) Sorted Lists
Question
Q1.Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.
Example:
Input: 1->2->4, 1->3->4 Output: 1->1->2->3->4->4
Q2.Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
Example:
Input: [ 1->4->5, 1->3->4, 2->6 ] Output: 1->1->2->3->4->4->5->6
在解決將k個已排序(從小到大)好的鏈表合並成一個已排序(從小到大)的鏈表前,我們先從一個特例開始,即將兩個鏈表合並成一個。
Answer
1.合並兩個排列好的鏈表
問題就是將兩個已排序的鏈表合並成一個,這是歸並排序裏的一個基本操作。
現在描述以下解決這個問題的算法,用數組來描述。
我們輸入兩個數組,設為A,B,輸出輸出C,加入三個計數器Aptr,Bptr,Cptr,初始化為數組開頭。
取A[Aptr]和B[Bptr]的較小值拷貝到C的下一個位置,相應的計數器加一,重復該操作,直到某個計數器指向的索引值大於數組的邊界。
這時候代表一個數組的元素已經全部拷貝到C的相應位置了,我們將另一個數組剩下的元素拷貝到C裏。
回到Leetccode的這個問題,從鏈表的角度,計數器和鏈表元素可以用指針來代替,輸入和輸出數組也可以通過鏈表頭指針訪問,下面通過不完整的代碼實現這個算法的思想。
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// first_Node用來輸出鏈表, last_Node用作上面算法描述的計數器(即Cptr),也用作插入元素,代表輸出C的最後一個節點 ListNode*first_Node = NULL, *last_Node = NULL;
// 在兩個數組都沒被拷貝到C時的操作。 while (l1 != NULL && l2 != NULL) {
// 判斷A[Aptr](與l1->val一致),B[Bptr](與l2->val一致)哪個較小,將較小的插入到鏈表C的最後,Aptr和Bptr相應加一(相當於l1,l2)
if (l1->val > l2->val) { last_Node->next = l2; last_Node = last_Node->next; l2 = l2->next; } else { last_Node->next = l1; last_Node = last_Node->next; l1 = l1->next; } }
// 將剩下的元素插入到C的最後 if (l1 == NULL && l2 != NULL) { last_Node->next = l2; } else if (l1 != NULL && l2 == NULL) { last_Node->next = l1; }
// 返回鏈表C return first_Node; }
上面的代碼已經很能體現代碼的具體思想了,剩下的工作就是將代碼完善,將邊界的情況考慮周全,實際呢,這又是特別麻煩的,一個不小心就會出現問題,比如說訪問空指針。我們仔細考慮就會知道,上面代碼沒有對first_Node進行初始化,所以我們需要對first_Node判斷是否為空,空就要初始化為對應的節點。那麽在哪裏判斷呢,很容易想到,我們需要在判斷A[Aptr]和B[Bptr]哪個較小後加入,然而這還沒結束,將剩下的元素插入到C的最後 後的代碼也沒有對first_Node(last_Node)是否為空作判斷,可又有對last_Node進行操作,這是十分危險的,所以我們也要在這裏加入判斷。最後完整的代碼如下:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode *first_Node = NULL, *last_Node = NULL; while (l1 != NULL && l2 != NULL) { if (l1->val > l2->val) { if (first_Node == NULL) { first_Node = l2; last_Node = first_Node; } else { last_Node->next = l2; last_Node = last_Node->next; } l2 = l2->next; } else { if (first_Node == NULL) { first_Node = l1; last_Node = first_Node; } else { last_Node->next = l1; last_Node = last_Node->next; } l1 = l1->next; } } if (l1 == NULL && l2 != NULL) { if (first_Node == NULL) first_Node = l2; else last_Node->next = l2; } else if (l1 != NULL && l2 == NULL) { if (first_Node == NULL) first_Node = l1; else last_Node->next = l1; } return first_Node; }
2.合並k個排列好的鏈表
好了,有了上面的例子,我們合並k的鏈表也會有思路了,模仿上面算法的思路,我們可以每次將k個鏈表的最小值比較一遍,取最小值,將其拷貝到輸出鏈表C中,當然這是可行的,時間復雜度為O(kN),N為最後剩下鏈表的長度,想了想感覺這有點麻煩,所以我們不妨考慮另一種方法。
按照上一題的思路,其實我們可以兩兩鏈表合並,最後合並成一個鏈表,這樣我們就可以重用上面的代碼了。兩兩合並可以是鏈表1和鏈表2合並後的鏈表 再和鏈表3合並,以此類推,也可以是鏈表1和鏈表2合並為鏈表①,鏈表3和鏈表4合並為鏈表②,依次類推,①又和②合並……,如下圖所示。聰明的你一比較就知道後者的效率會高很多,因為合並的次數明顯變少了。因此,我們根據後者編寫代碼:
ListNode* mergeKLists(vector<ListNode*>& lists) {
// 遞歸終止的條件 if (lists.size() == 0) return NULL; if (lists.size() == 1) return lists[0]; // 返回鏈表頭節點指針
vector<ListNode*> tmpVec;
// 每兩個鏈表合並 for (int i = 0; i < lists.size()/2*2; i += 2) tmpVec.push_back(mergeTwoLists(lists[i], lists[i+1])); // 如果k為奇數,將剩下的最後一個加入到tmpVec中 if (lists.size()%2 != 0) tmpVec.push_back(lists[lists.size()-1]);
// 繼續合並 return mergeKLists(tmpVec); }
實際上,這樣的解決辦法稱為分治(divide and conquer),將一個大問題分為幾個子問題來解決,在編程裏經常用遞歸實現,上面就有所體現。
Leetcode Week3 Merge Two(k) Sorted Lists