目录
方法一、暴力解法
1 - 解题思路
2 - 解题方法
3 - Code
4 - 复杂度
5 - 总结
1 - 解题思路
题目给出两个链表(不带头结点)首节点指针list1和list2。
/*
Definition for singly-linked list.
struct ListNode {
int val;
struct ListNode *next;
};
*/
两个链表都是升序的,可以在原有的空间做修改,不需要创建新的空间,将两个链表连接起来,使用暴力解法需要对每个元素逐个比较。
链表问题考虑使用哑结点。
2 - 解题方法
创建两个结点指针 preprev和prev指向新的哑结点,另外两个指针list1和list2分别指向两个当前正在比较的结点。
通过修改prev->next将新的链表连接起来,prev->next指向较小的结点,然后修改prev,使其指向该结点。
同时修改list指向下一个结点,为下一次比较做准备。
循环终止条件是当list1或list2指向为NULL,此时让prev指向此时不为空的list即可。
函数返回prepre->next(即最终链表的首节点);
3 - Code
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { struct ListNode* preprev=(struct ListNode* )malloc(sizeof(struct ListNode)); struct ListNode* prev=preprev; while(list1!=NULL&&list2!=NULL) { if(list1->val<=list2->val) { prev->next=list1; list1=list1->next; } else { prev->next=list2; list2=list2->next; } prev=prev->next; } prev->next = list1 == NULL ? list2 : list1; //条件运算符 return preprev->next; }
4 - 复杂度
①时间复杂度:O(n)
②空间复杂度:O(1)
5 - 总结
①链表问题可以考虑使用哑结点(一般哑结点放在头结点和首节点之间,使每一个结点都有一个前驱结点,方便操作)。
方法二、递归
1 - 解题思路
2 - 解题方法
3 - Code
4 - 复杂度
5 - 总结
1 - 解题思路
需要解决的问题可以分成若干相同的子问题,因此可以用递归的算法求解。
list1和list2分别指向两个链表,假设list1指向的结点为较小的一个,那么此时要做的就是运算 list1-next=mergeTwoLists(list1->next,list2),即保留第一个结点为list1,接下来把list1后面的链表与list2合并起来再一次调用该函数。
2 - 解题方法
①这个问题的子问题是什么。合并链表。
②当前层要干什么事情。找到两个结点较小的一个,加入当前链表,并将其后续链表与另一个链表再递归一次,合并起来。
③递归出口。①list1为空时②list2为空时
想清楚这几点就好啦。 很多刚接触递归的同学习惯往深处想,就想想清楚下一层,下下层到底咋回事,千万别!这样永远也想不清楚的,你只要把当前层的事情干好,边界条件找好,深层自然而然就是对的。千万别想那么深。
④调用递归步骤
/*(1,1):代表第一次进入递归函数,并且从第一个口进入,并且记录进入前链表的状态
merge(1,1): 1->4->5->null, 1->2->3->6->null list1->next = (list1 ->next,list2)
merge(2,2): 4->5->null, 1->2->3->6->null list2->next = (list2 ->next,list1)
merge(3,2): 4->5->null, 2->3->6->null list2->next = (list2 ->next,list1)
merge(4,2): 4->5->null, 3->6->null list2->next = (list2 ->next,list1)
merge(5,1): 4->5->null, 6->null list1->next = (list1 ->next,list2)
merge(6,1): 5->null, 6->null list1->next = (list1 ->next,list2)
merge(7): null, 6->null list1->next = (list1 ->next,list2)
return l2 递归出口
l1.next --- 5->6->null, return l1
l1.next --- 4->5->6->null, return l1
l2.next --- 3->4->5->6->null, return l2
l2.next --- 2->3->4->5->6->null, return l2
l2.next --- 1->2->3->4->5->6->null, return l2
l1.next --- 1->1->2->3->4->5->6->null, return l1*/
3 - Code
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { if(list1==NULL) return list2; else if(list2==NULL) return list1; else if(list1->val<list2->val) { list1->next=mergeTwoLists(list1->next,list2); return list1; } else { list2->next=mergeTwoLists(list2->next,list1); return list2; } }
4 - 复杂度
①时间复杂度:O(n)
②空间复杂度:O(n)
5 - 总结
①
递归函数必须要有终止条件,否则会出错;
递归函数先不断调用自身,直到遇到终止条件后进行回溯,最终返回答案。②
终止条件:当两个链表都为空时,表示我们对链表已合并完成。
如何递归:我们判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)