一.反转链表(leetcode206题)
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
PS:这道题主要是看了Carl(B站UP主代码随想录)的讲解,这里附上链接:力扣 LeetCode
思路
如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。
其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示:
之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改表next指针的方向。
那么接下来看一看是如何反转呢?
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
代码
解法1:双指针法
// 双指针法:
struct ListNode* reverseList(struct ListNode* head){
//保存cur的下一个结点
struct ListNode* temp;
//pre指针指向前一个当前结点的前一个结点
struct ListNode* pre = NULL;
//用head代替cur,也可以再定义一个cur结点指向head。
while(head) {
//保存下一个结点的位置
temp = head->next;
//翻转操作
head->next = pre;
//更新结点
pre = head;
head = temp;
}
return pre;
}
解法2:递归法(在双指针法的基础上实现,思路非常巧妙)
//递归法
struct ListNode* reverse(struct ListNode* pre, struct ListNode* cur) {
if(!cur)
return pre;
struct ListNode* temp = cur->next;
cur->next = pre;
//将cur作为pre传入下一层
//将temp作为cur传入下一层,改变其指针指向当前cur
return reverse(cur, temp);
}
struct ListNode* reverseList(struct ListNode* head){
return reverse(NULL, head);
}
二.合并两个有序链表(leetcode21题)
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = [] 输出:[]
示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
思路1:
递归解法:递归函数必须要有终止条件,否则会出错;
递归函数先不断调用自身,直到遇到终止条件后进行回溯,最终返回答案。
根据以上规律考虑本题目:
终止条件:当两个链表都为空时,表示我们对链表已合并完成。
如何递归:我们判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)
代码:
C语言代码如下:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1 == NULL) return list2;
if(list2 == NULL) return list1;
if (list1->val < list2->val){
list1->next = mergeTwoLists(list1->next, list2);
return list1;
}
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
关于递归,主要想清楚几点问题就行了 1.这个问题的子问题是什么。2.当前层要干什么事情。3.递归出口。想清楚这几点就好啦。 很多刚接触递归的同学习惯往深处想,就想想清楚下一层,下下层到底咋回事,千万别!这样永远也想不清楚的,你只要把当前层的事情干好,边界条件找好,深层自然而然就是对的。千万别想那么深。
赠上评论区大佬的理解:
思路2:
遍历两个链表,但是可能出现:一个遍历完毕,另一个还有剩余的情况,这种情况就把剩余的直接接上去(因为是 升序链表)
代码:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode *ret, *tmp; //创建一个哨兵结点(类似于射线的端点,只是不存值)和一个临时指针
ret = malloc(sizeof (struct ListNode));//给哨兵结点分配空间,但他存的是 NULL
tmp = ret;//先给临时指针赋初值(最开始指向哪里)
while(list1 && list2){//当连个链表中没有遍历完毕的,就继续循环遍历
if (list2->val > list1->val){//谁小谁在前,list1小 list1在前
tmp->next = list1;//临时指针指向 list1
tmp = list1;//赋值
list1 = list1->next;// list1 向后移1位
}
else//否则就反过来呗~ list2 小看 list2
{
tmp->next = list2;
tmp = list2;
list2 = list2->next;
}
}
//下面的if else 语句承接第4行和第10行,剩下的直接拼接在后面就可以了
//同时,这个也省下了“边界判断”(即:如果两个链表其中有一个是 空 怎么办?)。这里表示:直接接上不就好了
if(list1)
tmp->next = list1;
else
tmp->next = list2;
return ret->next;//这里,ret是哨兵,里面存的是 NULL,返回咱们自己设的这个哨兵没意义,所以返回 next
}
作者:探险男孩和他的大白鹅
链接:https://leetcode.cn/problems/merge-two-sorted-lists/solutions/1649563/by-xenodochial-faradaywef-nqfr/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
思路3:
新创建一个链表,把一个个节点插进去
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode* l3 = (struct ListNode *)malloc(sizeof(struct ListNode));
struct ListNode* p1 = l1, * p2 = l2;
struct ListNode* r3 = l3;
while(p1 && p2){
if((p1 -> val) <= ( p2 -> val)){
r3 -> next = p1;
r3 = p1;
p1 = p1 -> next;
}
else{
r3 -> next = p2;
r3 = p2;
p2 = p2->next;
}
}
if(!p1)
{ r3 -> next = p2 ; }
else if(!p2)
{ r3 -> next = p1; }
return l3->next;
}