23-合併K個升序連結串列
題目:
給你一個連結串列陣列,每個連結串列都已經按升序排列。
請你將所有連結串列合併到一個升序連結串列中,返回合併後的連結串列。
示例 1:
輸入:lists = [[1,4,5],[1,3,4],[2,6]]
輸出:[1,1,2,3,4,4,5,6]
解釋:連結串列陣列如下:
[
1->4->5,
1->3->4,
2->6
]
將它們合併到一個有序連結串列中得到。
1->1->2->3->4->4->5->6
示例 2:
輸入:lists = []
輸出:[]
示例 3:
輸入:lists = [[]]
輸出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的總和不超過 10^4
解答:
解答一:在一個迴圈中,依次將兩個連結串列合併,合併後的結果放到其中一個連結串列,另一個連結串列設定為nullptr,程式碼如下,
ListNode* mergeTwoList(ListNode* l1, ListNode* l2) { if (l1 == nullptr&&l2 == nullptr) return nullptr; if (l1 == nullptr) return l2; if (l2 == nullptr) return l1; ListNode* result = newListNode(); ListNode* tmp = result; while (l1 != nullptr || l2 != nullptr) { if (l1 == nullptr) { tmp->next = l2; l2 = l2->next; tmp = tmp->next; continue; } if (l2 == nullptr) { tmp->next = l1; l1 = l1->next; tmp = tmp->next; continue; } if (l1->val < l2->val) { tmp->next = l1; l1 = l1->next; tmp = tmp->next; continue; } else { tmp->next = l2; l2 = l2->next; tmp = tmp->next; continue; } } return result->next;; } ListNode* mergeKLists(vector<ListNode*>& lists) { if (lists.size() == 0) return nullptr; else if (lists.size() == 1) return lists.at(0); //從兩側向中間移動,兩兩合併 int idx1 = 0; int idx2 = lists.size()-1; ListNode* result = nullptr; while (idx1 < idx2) { while (lists.at(idx1) == nullptr && idx1 < idx2)//list中可能有空連結串列,刪除裡面的空連結串列 { idx1++; } while (lists.at(idx2) == nullptr && idx1 < idx2) { idx2--; continue; } if (idx1 == idx2) { if (result == nullptr && lists.at(idx1) != nullptr)//去重後,兩個idx可能重複,但是重複的可能是唯一一個不為nullptr的list return lists.at(idx1); break; } //合併兩個連結串列 result = mergeTwoList(lists.at(idx1), lists.at(idx2)); lists.at(idx1) = result; idx2--; } return result; }
提交後通過,但是合併次數為N-1,修改如下
解答二:讓vector中的元素兩兩合併,合併後再遞迴呼叫,減少合併次數,分治法
vector<ListNode*> mergeSubStep(vector<ListNode*> lists) { vector<ListNode*> vec; for (auto val : lists) { if (val == nullptr) continue; vec.push_back(val); } if (vec.size() < 2) return vec; vector<ListNode*> tmpVec; int idx1 = 0, idx2 = vec.size() - 1; while (idx1 < idx2) { ListNode* result = mergeTwoList(vec.at(idx1), vec.at(idx2)); tmpVec.push_back(result); idx1++; idx2--; } if (vec.size() % 2 == 1)//奇數個,把中間的新增到vec { tmpVec.push_back(vec.at(idx1)); } return tmpVec; } ListNode* mergeKLists2(vector<ListNode*>& lists) { vector<ListNode*> result = mergeSubStep(lists); if (result.size() == 0) return nullptr; else if (result.size() == 1) return result.at(0); while (true) { result = mergeSubStep(result); if (result.size() < 2) break; } return result.at(0); }
其中,兩個連結串列合併的程式碼跟解答一中的一樣,省略。提交後,相對於解答一,速度提高了很多。
看解答。
一:合併兩個連結串列,解答中的效率較高,程式碼如下:
ListNode* mergeTwoLists(ListNode *a, ListNode *b) { if ((!a) || (!b)) return a ? a : b; ListNode head, *tail = &head, *aPtr = a, *bPtr = b; while (aPtr && bPtr) { if (aPtr->val < bPtr->val) { tail->next = aPtr; aPtr = aPtr->next; } else { tail->next = bPtr; bPtr = bPtr->next; } tail = tail->next; } tail->next = (aPtr ? aPtr : bPtr); return head.next; }
該函式中,判斷是否為nullptr以及比較很少,在兩個連結串列都不為空時取較小值,當其中一個為nullptr後,直接將next指向另一個連結串列即可。而自己寫的合併在一個while中多次判斷連結串列是否為nullptr,效率較低。
二:解答中的分治使用兩個index,這樣在遞迴呼叫時不會產生中間的vector,以節約記憶體:而且求L+R的平均值時,使用的是右移操作,比除2效率高。
ListNode* merge(vector <ListNode*> &lists, int l, int r) { if (l == r) return lists[l]; if (l > r) return nullptr; int mid = (l + r) >> 1; return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r)); } ListNode* mergeKLists(vector<ListNode*>& lists) { return merge(lists, 0, lists.size() - 1); }