1. 程式人生 > >LeetCode 23 ——合併 K 個排序連結串列

LeetCode 23 ——合併 K 個排序連結串列

1. 題目

23

2. 解答

2.1. 方法一
  • 合併兩個有序連結串列 的基礎上,我們很容易想到第一種解法,首先我們將第一個連結串列和第二個連結串列合併成一個新的連結串列,然後再往後依次合併接下來的每個連結串列即可。

  • 假設每個連結串列結點數一樣都為 n,第一次合併時,要遍歷 2n 個結點,往後則要分別遍歷 3n, 4n, ... , kn 個結點。可以看到,每次進行合併時都要將之前所有的連結串列遍歷一次,因此這個方法的時間複雜度較高,為 $O((\frac{k(k+1)}{2} - 1) * n)$。

  • 程式碼如下
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        
        int k = lists.size(); // 連結串列個數
        
        if (k >= 2)
        {   
            // 先將前兩個連結串列合併為一個新連結串列,再把新連結串列依次和後面的連結串列合併
            ListNode *head = mergeTwoLists(lists[0], lists[1]);
            for (int i = 2; i < k; i++)
            {
                head = mergeTwoLists(head, lists[i]);
            }
            
            return head;  
        }
        else if (k == 1)
        {
            return lists[0];
        } 
        else
        { 
            return NULL;
        }
        
    }
    
        ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        
        ListNode *head = new ListNode(0); // 新建哨兵結點,方便操作
        ListNode *temp = head;
        
        // 依次比較兩個連結串列的結點值,將值較小的結點插入到新建的連結串列後面
        while(l1 && l2)
        {
            if (l2->val <= l1->val)
            {
                temp->next = l2;
                temp = temp->next;
                l2 = l2->next;
            }
            else
            {
                temp->next = l1;
                temp = temp->next;
                l1 = l1->next;
            }
        }
        
        // 其中一個連結串列比較完畢,將另外一個連結串列剩餘結點直接插入到新建的連結串列後面
        if (l1)
        {
            temp->next = l1;
        }
        else
        {
            temp->next = l2;
        }
        
        temp = head;
        head = head->next;// 刪除哨兵結點
        delete(temp);
        
        return head;  
    }
};
2.2. 方法二
  • 我們還可以每次只合並相鄰的兩個連結串列,這樣經過第一次合併後,連結串列個數減半,我們一直重複這個過程,直到最後剩餘一個連結串列即可。

  • 假設每個連結串列結點數一樣都為 n,第一次合併時,要進行 $\frac{k}{2}$ 次合併,每次合併要遍歷 2n 個結點;第二次合併時,要進行 $\frac{k}{4}$ 次合併,每次合併要遍歷 4n 個結點;可見,每次合併過程都只需要遍歷 kn 次結點。而總共要進行 $log_2k$ 個迴圈,因此這個方法的時間複雜度為 $O(n * klog_2k)$,比第一個改善了許多。

  • 程式碼如下
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        
        int k = lists.size(); // 連結串列個數
        
        if (k >= 2)
        {   
            while(k != 1) // 迴圈合併過程直到剩餘一個連結串列
            {
                if (k % 2 == 0)  // 總共偶數個連結串列,依次合併相鄰兩個連結串列然後從前往後放到 vector 中去
                {
                    for (int i = 0; i < k; i=i+2)
                    {
                        lists[i / 2] = mergeTwoLists(lists[i], lists[i+1]);
                    }
                    k = k / 2; // 每次合併後的新連結串列個數
                }
                
                else // 總共奇數個連結串列,依次合併相鄰兩個連結串列然後從前往後放到 vector 中去,最後餘一個連結串列直接放入 vector 最後面
                {
                    for (int i = 0; i < k - 1; i=i+2)
                    {
                        lists[i / 2] = mergeTwoLists(lists[i], lists[i+1]);
                    }
                    lists[k / 2] = lists[k-1];
                    k = k / 2 + 1; // 每次合併後的新連結串列個數
                }
            }
            
            return lists[0]; 
                  
        }
        else if (k == 1)
        {
            return lists[0];
        } 
        else
        { 
            return NULL;
        }
        
    }
    
        ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        
        ListNode *head = new ListNode(0); // 新建哨兵結點,方便操作
        ListNode *temp = head;
        
        // 依次比較兩個連結串列的結點值,將值較小的結點插入到新建的連結串列後面
        while(l1 && l2)
        {
            if (l2->val <= l1->val)
            {
                temp->next = l2;
                temp = temp->next;
                l2 = l2->next;
            }
            else
            {
                temp->next = l1;
                temp = temp->next;
                l1 = l1->next;
            }
        }
        
        // 其中一個連結串列比較完畢,將另外一個連結串列剩餘結點直接插入到新建的連結串列後面
        if (l1)
        {
            temp->next = l1;
        }
        else
        {
            temp->next = l2;
        }
        
        temp = head;
        head = head->next;// 刪除哨兵結點
        delete(temp);
        
        return head;  
    }
};
2.3. 方法三
  • 利用 C++ 模板庫中的優先佇列構建小頂堆,每次首結點元素值最小的連結串列出隊,然後將首結點插入到新連結串列後面,再把刪除首結點後的子連結串列放入佇列中繼續比較,直到佇列為空結束。

  • 程式碼如下
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        
        int k = lists.size();
        
        struct cmp
        {
            bool operator()(ListNode* a, ListNode* b)
            {
                return a->val > b->val;
            }
        };

        priority_queue<ListNode *, vector<ListNode*>, cmp> q; // 構建小頂堆

        for (int i = 0; i < k; i++)
        {
            if (lists[i])
                q.push(lists[i]);  // 連結串列非空,加入佇列
        }

        ListNode *head = new ListNode(0); // 新建哨兵結點,方便操作
        ListNode *temp = head;

        while(!q.empty())
        {
            temp->next = q.top(); // 將元素值最小的首結點插入到新連結串列後面
            temp = temp->next; // 指標後移
            q.pop();        // 首結點元素值最小的連結串列出隊
            if (temp->next)  // 把刪除首結點後的連結串列放入佇列中
                q.push(temp->next);
        }

        temp = head;
        head = head->next;
        delete(temp);  // 刪除哨兵結點

        return head;
    }
};

獲取更多精彩,請關注「seniusen」! seniusen