23. Merge k Sorted Lists

原创 2015年07月07日 13:04:26

这道题目,自己犯了懒惰病,以至于几乎没怎么好好思考过,结果就导致了连最基本的解法都没有想出来。其实这道题,解法共有三种。下面分别进行阐述。

解法1:最基础也最符合直觉的解法就是先合并1、2,12结果和3合并,123结果和4合并.....123...k-1结果和k合并,我们计算一下复杂度。在这里,我们假设已排序链表的平均长度为n。

1、2合并,遍历2n个节点。

12结果和3合并,遍历3n个节点。

123结果和4合并,遍历4n个节点。

...

123...k-1结果和k合并,遍历kn个节点

总共遍历的节点数目为n(2+3+...+k) = n*(k^2+k-2)/2, 因此时间复杂度是O(n*(k^2+k-2)/2) = O(nk^2),代码如下:

ListNode *mergeKLists(vector<ListNode *> &lists) {
        if(lists.size() == 0)return NULL;
        ListNode*res = lists[0];
        for(int i = 1; i < lists.size(); i++)
            res = merge2list(res, lists[i]);
        return res;
    }
    
    ListNode *merge2list(ListNode *head1, ListNode*head2)
    {
        ListNode node(0), *res = &node;
        while(head1 && head2)
        {
            if(head1->val <= head2->val)
            {
                res->next = head1;
                head1 = head1->next;
            }
            else
            {
                res->next = head2;
                head2 = head2->next;
            }
            res = res->next;
        }
        if(head1)
            res->next = head1;
        else if(head2)
            res->next = head2;
        return node.next;
    }

解法2: 利用分治的思想把合并k个链表分为两个合并k/2个链表的任务,一直划分,直到任务中只剩下一个链表或者两个链表。可以很简单的用递归来实现。因此算法复杂度为T(k) = 2T(k/2) + O(nk),根据主定理可以得到算法的复杂度为O(nklogk)。

递归的代码就不贴了。下面是非递归的代码。非递归的思想是(以四个链表为例):

0、2合并,合并结果放到0的位置

1、3合并,合并结果放到1的位置

再把0、1合并(相当于原来的13 和 24合并)。

ListNode *mergeKLists(vector<ListNode *> &lists) {
        int n = lists.size();
        if(n == 0)return NULL;
        while(n >1)
        {
            int k = (n+1)/2;
            for(int i = 0; i < n/2; i++)
                lists[i] = merge2list(lists[i], lists[i + k]);
            n = k;
        }
        return lists[0];
    }
     
    ListNode *merge2list(ListNode *head1, ListNode*head2)
    {
        ListNode node(0), *res = &node;
        while(head1 && head2)
        {
            if(head1->val <= head2->val)
            {
                res->next = head1;
                head1 = head1->next;
            }
            else
            {
                res->next = head2;
                head2 = head2->next;
            }
            res = res->next;
        }
        if(head1)
            res->next = head1;
        else if(head2)
            res->next = head2;
        return node.next;
    }

解法3:维护一个k个大小的最小堆,初始化堆中元素为每个链表的头结点,每次从堆中选择最小的元素加入到结果链表,再选择该最小元素所在链表的下一个节点加入到堆中。这样当堆为空时,所有链表的节点都已经加入了结果链表。元素加入堆中的复杂度为O(logk),总共有kn个元素加入堆中,因此,复杂度也和算法2一样是O(nklogk)。代码如下:

class Solution {
private:
struct cmp // 比较类
{
    bool operator ()(const ListNode *a, const ListNode *b) // 从小到大排列
    {
            return a->val > b->val;
    }
};
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        int n = lists.size();
        if(n == 0)return NULL;
        ListNode node(0), *res = &node;// 一个trick,在这里,res充当移动的cursor
        priority_queue<ListNode*, vector<ListNode*>, cmp> que;
        for(int i = 0; i < n; i++)
            if(lists[i])
                que.push(lists[i]);
        while(!que.empty())
        {
            ListNode * p = que.top();
            que.pop();
            res->next = p;
            res = p; // 注意,在代码中,没有写res->next = NULL这样的语句,res最后指向的那个节点,其next一定为NULL
             
            if(p->next)// 向que中加入剩下的元素
                que.push(p->next);
        }
        return node.next;
    }
};


更新------------------------------

思路1:priority queue

将每个list的最小节点放入一个priority queue (min heap)中。之后每从queue中取出一个节点,则将该节点在其list中的下一个节点插入,以此类推直到全部节点都经过priority queue。由于priority queue的大小为始终为k,而每次插入的复杂度是log k,一共插入过nk个节点。时间复杂度为O(nk logk),空间复杂度为O(k)

注意C++的STL中的priority queue默认是max heap,定义一个新的比较函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
    struct compNode {
        bool operator()(ListNode *p, ListNode *q) const {
            return p->val>q->val;
        }  
    };

    ListNode *mergeKLists(vector<ListNode *> &lists) {
        priority_queue<ListNode*, vector<ListNode*>, compNode> pq;
        ListNode *dummy = new ListNode(0), *tail = dummy;
        
        for(int i=0; i<lists.size(); i++) 
            if(lists[i]) pq.push(lists[i]);
            
        while(!pq.empty()) {
            tail->next = pq.top();
            tail = tail->next;
            pq.pop();
            if(tail->next) pq.push(tail->next);
        }
        
        return dummy->next;
    }
};


思路2:Merge two lists

利用合并两个list的方法,依次将每个list合并到结果的list中。这个方法的空间复杂度为O(1),时间复杂度为:
2n + 3n + ... + kn = [(k+1)*k/2-1]*n = O(nk^2)
由于时间复杂度较大,大数据测试会超时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        ListNode *ret = NULL;
        for(int i=0; i<lists.size(); i++) 
            ret = merge2Lists(ret, lists[i]);
        return ret;
    }
    
    ListNode* merge2Lists(ListNode *h1, ListNode *h2) {
        ListNode *dummy = new ListNode(0), *tail = dummy;
        while(h1 && h2) {
            if(h1->val<=h2->val) {
                tail->next = h1;
                h1 = h1->next;
            }
            else {
                tail->next = h2;
                h2 = h2->next;
            }
            tail = tail->next;
        }
        tail->next = h1 ? h1 : h2;
        return dummy->next;
    }
};


思路3:二分法

类似merge sort,每次将所有的list两两之间合并,直到所有list合并成一个。如果用迭代而非递归,则空间复杂度为O(1)。时间复杂度:
2n * k/2 + 4n * k/4 + ... + (2^x)n * k/(2^x) = nk * x
k/(2^x) = 1 -> 2^x = k -> x = log2(k)
所以时间复杂度为O(nk log(k)),与方法一相同。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        if(lists.empty()) return NULL;
        int end = lists.size()-1;
        while(end>0) {
            int begin = 0;
            while(begin<end) {
                lists[begin] = merge2Lists(lists[begin], lists[end]);
                begin++;
                end--;
            }
        }
        return lists[0];
    }
    
    ListNode* merge2Lists(ListNode *h1, ListNode *h2) {
        ListNode *dummy = new ListNode(0), *tail = dummy;
        while(h1 && h2) {
            if(h1->val<=h2->val) {
                tail->next = h1;
                h1 = h1->next;
            }
            else {
                tail->next = h2;
                h2 = h2->next;
            }
            tail = tail->next;
        }
        tail->next = h1 ? h1 : h2;
        return dummy->next;
    }
};




23. Merge k Sorted Lists等题 第二周解题报告

原题Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity....
  • Mr_Zing
  • Mr_Zing
  • 2016年09月11日 23:47
  • 565

leetcode23~Merge k Sorted Lists Add to List

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 把...

【leetcode】23. Merge k Sorted Lists

合并多个已排序的单链表,多种实现方式

[leetcode] 23. Merge k Sorted Lists

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. ...

leetcode_23_Merge k Sorted Lists

思路: 本来想模仿合并两个链表的方法,查找所有的lists链表一遍找出一个最小值,并将最小值链接到结果链表中,并将最小结点删除,将最小结点的next结点替代之,直至为null,将链表删去。 但是当l...

LeetCode_OJ【23】Merge k Sorted Lists

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. ...
  • hb_peng
  • hb_peng
  • 2015年09月16日 20:46
  • 246

leetcode--23. Merge k Sorted Lists

leetcode

leetcode23---Merge k Sorted Lists

问题描述:有k个已经排好序的链表,将其合并为一个有序链表。代码:#include #include #include using namespace std;struct ListNode { ...
  • will130
  • will130
  • 2015年12月18日 23:28
  • 179

LeetCode 23 Merge k Sorted Lists

这是一道基本的归并排序问题,设定步长d初始为1,然后以指数增长归并向量元素 每次归并的思想就是将后一个链表的元素插入前一个链表,如果前一个链表为空,则前一个链表直接指向后一个链表 当d大于...
  • earthma
  • earthma
  • 2015年04月08日 09:49
  • 357

Leetcode-23.Merge k Sorted Lists

Problem Description: Merge k sorted linked lists and return it as one sorted list. Analyze and desc...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:23. Merge k Sorted Lists
举报原因:
原因补充:

(最多只允许输入30个字)