[leetcode]Merge k Sorted Lists

本文介绍了两种合并k个有序链表的方法,一种采用分治法,时间复杂度为O(nklogk),空间复杂度为O(logk);另一种利用大小为k的堆,时间复杂度同样为O(nklogk),但空间复杂度为O(k)。

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

这道题目在分布式系统中非常常见,来自不同client的sorted list要在central server上面merge起来。这个题目一般有两种做法,下面一一介绍并且分析复杂度。 第一种做法比较容易想到,就是有点类似于MergeSort的思路,就是分治法,不了解MergeSort的朋友,请参见 归并排序-维基百科 ,是一个比较经典的O(nlogn)的排序算法,还是比较重要的。思路是先分成两个子任务,然后递归求子任务,最后回溯回来。这个题目也是这样,先把k个list分成两半,然后继续划分,知道剩下两个list就合并起来,合并时会用到 Merge Two Sorted Lists 这道题。
我们来分析一下上述算法的时间复杂度。假设总共有k个list,每个list的最大长度是n,那么运行时间满足递推式T(k) = 2T(k/2)+O(n*k)。根据主定理,可以算出算法的总复杂度是O(nklogk)。如果不了解主定理的朋友,可以参见 主定理-维基百科 。空间复杂度的话是递归栈的大小O(logk)。 
/**
 * 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) {
        if(lists.size()==0) return NULL;
        return helper(lists,0,lists.size()-1);
    }
    ListNode *helper(vector<ListNode *> &lists,int s,int t){
        if(s<t){
            int m=(s+t)/2;
            return merge(helper(lists,s,m),helper(lists,m+1,t));
        }
        else{
            return lists[s];
        }
    }
    ListNode *merge(ListNode *p1,ListNode *p2){
        ListNode *dummy=new ListNode(-1);
        ListNode *cur=dummy;
        while(p1&&p2){
            if(p1->val<p2->val){
                cur->next=p1;
                p1=p1->next;
                cur=cur->next;
            }
            else{
                cur->next=p2;
                p2=p2->next;
                cur=cur->next;
            }
        }
        while(p1){
            cur->next=p1;
            p1=p1->next;
            cur=cur->next;
        }
        while(p2){
            cur->next=p2;
            p2=p2->next;
            cur=cur->next;
        }
        return dummy->next;
    }

};

接下来我们来看第二种方法。这种方法用到了堆的数据结构,思路比较难想到,但是其实原理比较简单。维护一个大小为k的堆,每次去堆顶的最小元素放到结果中,然后读取该元素的下一个元素放入堆中,重新维护好。因为每个链表是有序的,每次又是去当前k个元素中最小的,所以当所有链表都读完时结束,这个时候所有元素按从小到大放在结果链表中。这个算法每个元素要读取一次,即是k*n次,然后每次读取元素要把新元素插入堆中要logk的复杂度,所以总时间复杂度是O(nklogk)。空间复杂度是堆的大小,即为O(k)。代码如下:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
struct cmp {
    bool operator() (ListNode *a, ListNode *b) {
        return a->val > b->val;
    }
};
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        priority_queue<ListNode*,vector<ListNode*>,cmp> heap;
        for(int i=0;i<lists.size();i++){
            if(lists[i]!=NULL){
                heap.push(lists[i]);
            }
        }
        ListNode *head=NULL;
        ListNode *prev=NULL;
        ListNode *tmp;
        while(!heap.empty()){
            tmp=heap.top();
            heap.pop();
            if(prev==NULL){
                head=tmp;
            }
            else{
                prev->next=tmp;
            }
            prev=tmp;
            
            if(tmp->next!=NULL){
                heap.push(tmp->next);
            }
        }
        return head;
    }
};

http://zh.wikipedia.org/wiki/%E4%B8%BB%E5%AE%9A%E7%90%86


内容概要:本文是一份关于“Soldiers”问题的算法设计与分析实践报告,研究如何将分布在网格点上的n个士兵重排成一条水平队列,使得总体移动步数最少。解决方案基于中位数性质,分别处理x和y方向的最优位置:y坐标取所有士兵y坐标的中位数,x坐标通过预调整(原始x坐标减去对应索引)后再取中位数来确定最佳起始位置。算法时间复杂度为O(n logn),主要开销来自三次排序操作;空间复杂度为O(n),用于存储坐标数据。实验验证了算法在样例输入下的正确性,得出最小移动步数为8,符合预期输出。报告还指出了实现过程中常见的索引处理错误及偶数情况下中位数选择的一致性问题,并建议加强极端情况的测试覆盖。; 适合人群:具备一定算法基础、正在学习算法设计与分析的计算机相关专业学生或初级开发者,尤其是对中位数优化、贪心策略和排序应用感兴趣的人员。; 使用场景及目标:①掌握利用中位数求解曼哈顿距离最小和的经典方法;②理解如何将二维问题分解为两个独立的一维问题进行优化;③学习排序与索引变换在实际算法中的巧妙应用;④提升对边界条件和代码细节的调试能力。; 阅读建议:此资源侧重于算法思想的理解与实现细节的剖析,建议读者结合代码逐步调试,重点关注x坐标调整与重新排序的过程,同时自行构造多种测试用例(包括边界和极端情况)以加深理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值