链接: https://leetcode-cn.com/problems/merge-k-sorted-lists/.
归并法
今天只做了这一个算法题,做到我快自闭了~先是理解归并的思想,接着在编好程序后,出现指针指向随机内存的问题;好不容易解决了指针的问题,多次提交,还总是出现超时的问题。反复检查代码,居然是在merge时候,左半边的list总是从0号链表开始merge的,我要炸了!果然还是太菜了。
写下在编程中遇到的问题,以及苦苦探寻解决办法的过程,希望能帮助到大家。
合并两个有序链表
首先,我们考虑只合并2个有序链表的合并的情况:
- 定义一个新的头结点pHead ,用于保存合并后的新链表的头部;
- 定义一个变量p,用于指向下一个插入位置的前一个位置(),pa,pb分别指两个链表待合并的剩下部分的第一个结点
- 当pa,pb不为空的时候,判断它们val值的大小进行合并;如果pa为空了,则将剩下的pb链表直接合并到新链表的后面;如果pb为空,则将剩下的pa链表直接合并到新链表后面;
- 在合并的过程中,将要合并的结点添加到p->nextt,然后改变p的指向,p=p->next;
代码:
//合并两个有序链表
ListNode* mergeTwoList(ListNode* a,ListNode* b){
if((!a)||(!b)) return a?a:b;
ListNode pHead,*p=&pHead,*pa=a,*pb=b;
//ListNode* pHead,*p=pHead;这样定义的两个变量都是一个指针,它们指向一个随机的地址,
//如果事先不给一个确切的ListNode的地址给他们俩,
//直接运行程序,可能会造成它们俩指向的内存被破坏,导致程序终止运行
//所以应该要先定义一个ListNode pHead的类型的变量,系统给这个变量分配了内存,然后再定义*p指向这个内存
while(pa&&pb){
if(pa->val<pb->val){
p->next=pa;
pa=pa->next;
}
else{
p->next=pb;
pb=pb->next;
}
p=p->next;
}
p->next=(pa?pa:pb);
return pHead.next;
}
在第一次编程的时候,写成了ListNode* pHead,*p=pHead;,导致程序运行时停止了。仔细分析代码,发现这样定义个是两个指针变量,我并没有给他们一个准确内存给他们去指向,它们现在是随机指向了一个内存(这个内存有时是不允许直接访问的),导致在后面做p->next=pa或p->next=pb的时候,去改变一个不知道的内存的值,可能就会导致程序终止。因此,我后面改成了ListNode pHead,*p=&pHead,先定义了一个ListNode pHead的类型的变量,系统给这个变量分配了内存,然后再定义*p指向这个内存
合并k个有序链表
了解合并2个有序链表的过程后,再来理解如何用分治的方法合并k个有序链表,假设每个链表的最长长度为n:
- .将k个链表两两配对,并将配对的链表进行合并;
- 经过第一轮后,k个的链表合并成了 k 2 \frac{k}{2} 2k ,平均长度 2 k k \frac{2k}{k} k2k 为;然后再两两配对,合并成 k 4 \frac{k}{4} 4k 个链表, k 8 \frac{k}{8} 8k个链表 等等;
- 重复上述过程,直到将k个链表合并成1个有序链表。
代码:
//分治法:每次将链表集合分成两半,分别对两半进行合并,两半都合并好后,再对整体进行合并
ListNode* merge(vector<ListNode*> &list,int left,int right){
if(left==right) return list[left];
if(left>right) return NULL;
int mid=(left+right)>>1;//注意这里不能写成list.size()/2,因为list的size是固定的,而我们每次分而治之,数量都在减半,因此要用最新的left和right的中值,从而实现分而治之
return mergeTwoList(merge(list,left,mid),merge(list,mid+1,right));//mergeTwoList(merge(list,0,mid),merge(list,mid+1,right));不能从0开始merge,而应该从left
}
ListNode* mergeKLists(vector<ListNode*> &lists){
return merge(lists,0,lists.size()-1);
}
在编程过程中,总会遇到这样那样的问题。在取中间值mid的时候,一开始写成了mid=list.size()/2,实际上这里list传过来的是一个引用,和最开始的list是同一个东西,所以后面不管递归多少层,list的大小都是最原始的那个list的大小,即size为k。而我们每次分而治之,数量都在减半,因此要用最新的left和right的中值,从而实现分而治之。同理,merge(list,mid+1,right));不能从0开始merge,而应该从left。
编程中出现的问题,一般都是对算法思想理解不够透彻,所以…加油吧~
为了便于测试,我把创建链表的代码也贴上来了:
代码:
//链表结构
struct ListNode{
int val;
ListNode* next;
ListNode():val(0),next(NULL){
}
ListNode(int x):val(x),next(NULL){
}
};
//创建链表
ListNode* createList(int n){//n为链表中结点的数量
int x;
ListNode* pHead=new ListNode(0);
ListNode* pre=pHead;
for(int i=0;i<n;i++){
cin>>x;//依次输入链表结点
ListNode* p=new ListNode(x);
pre->next=p;
pre=p;
}
cout<<pHead<<endl;
cout<<pHead->val<<endl;
return pHead->next;
}
//输出链表
void print(ListNode* L){
ListNode* p=L;
while(p){
cout<<p->val<<"->";
p=p->next;
}
cout<<endl;
}
以上总结完毕。