链表小练习

一.反转链表(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值