1. 程式人生 > 實用技巧 >23-合併K個升序連結串列

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 = new
ListNode(); 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);
    }