23. Merge k Sorted Lists

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

解法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;
    }
};




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值