代码随想录算法训练营第四天|24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,142.环形链表II

24. 两两交换链表中的节点

题目

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
在这里插入图片描述

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

解题思路

  • 只要是涉及节点指向的操作,就一定要记得使用虚拟头节点,虚拟头节点的作用主要是为了使头节点的操作和后面节点的操作保持一致。不然,每次操作都要单独操作头结点,很麻烦。
  • 要操作第1、2各节点,那cur就一定要放在第1个节点前,操作第3个节点,cur就指向第2个节点。总之要一直保持在需要操作节点之前。
  • 额外补充一点:第1个节点就是头节点。

如下是代码随想录的画图解题思路,这里可能丢失的节点是1和3,因此需要先用临时变量进行保存:
在这里插入图片描述

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode *fakeNode = new ListNode(0);
        fakeNode->next = head;
        ListNode *cur = fakeNode;
        //必须先判断 cur->next 是否为空,否则如果先判断 cur->next->next,如果cur->next为空则会报错
        while (cur->next != nullptr && cur->next->next != nullptr) {
            // 先把cur之后的两个节点保存下来在临时变量中,以免丢失节点。
            ListNode *temp1 = cur->next;
            ListNode *temp2 = cur->next->next->next;

            // 步骤一:
            // 如果不用temp保存第1个节点,那此时赋值后,节点1就找不到了。
            // 原来是 cur->next ,现在 cur->next 已经指向了节点2
            cur->next = cur->next->next;

            //步骤二:
            //此时节点2是cur->next
            cur->next->next = temp1;

            //步骤三:
            temp1->next = temp2;

            //移动cur节点,进行下一次循环
            cur = cur->next->next;
        }
        return fakeNode->next;
    }
};

19.删除链表的倒数第N个节点

题目

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
在这里插入图片描述
示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

解题思路

  • 最简单的方法,遍历两遍,第一遍确定节点总数num,第二遍删除第 num-n+1 个节点
  • 另一种思路来自于代码随想录,使用双指针方法,如下图所示
    在这里插入图片描述
  • 对于临界值需要注意的是,如果是删除倒数第 n 个数,那么能确定至少有 n 个数,临界值自然就是 num = n 的情况

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *fakeNode = new ListNode(0);
        fakeNode->next = head;
        ListNode *fastNode = fakeNode, *slowNode = fakeNode;
        while (n && fastNode != nullptr) {
            fastNode = fastNode->next;
            n--;
        }
        fastNode = fastNode->next;
        while (fastNode != nullptr) {
            fastNode = fastNode->next;
            slowNode = slowNode->next;
        }
        slowNode->next = slowNode->next->next;
        return fakeNode->next;
    }
};

142.环形链表II

题目

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:
在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
在这里插入图片描述

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
在这里插入图片描述

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

解题思路

(这题自己实在没搞出来,直接看大神的解析过程吧)
这类链表题目一般都是使用双指针法解决的,例如寻找距离尾部第 K 个节点、寻找环入口、寻找公共尾部入口等。

在本题的求解过程中,双指针会产生两次“相遇”。

双指针的第一次相遇:
设两指针 fastslow 指向链表头部 head
令 fast 每轮走 2 步,slow 每轮走 1 步。

执行以上两步后,可能出现两种结果:

  • 第一种结果: fast 指针走过链表末端,说明链表无环,此时直接返回 null
    如果链表存在环,则双指针一定会相遇。因为每走 1 轮,fast 与 slow 的间距 +1,fast 一定会追上 slow
  • 第二种结果: 当fast == slow时, 两指针在环中第一次相遇。下面分析此时 fastslow 走过的步数关系:

设链表共有 a+b 个节点,其中 链表头部到链表入口 有 a 个节点(不计链表入口节点), 链表环 有 b 个节点(这里需要注意,ab 是未知数,例如图解上链表 a=4 , b=5);设两指针分别走了 fs 步,则有:

  • fast 走的步数是 slow 步数的 2 倍,即 f=2s;
  • fastslow 多走了 n 个环的长度,即 f=s+nb;( 解析: 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fastslow 多走 环的长度整数倍 )。
    将以上两式相减得到 f=2nb,s=nb,即 fastslow 指针分别走了 2nn 个环的周长。

接下来该怎么做呢?

如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nb,即先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点。而目前 slow 指针走了 nb 步。因此,我们只要想办法让 slow 再走 a 步停下来,就可以到环的入口。

但是我们不知道 a 的值,该怎么办?依然是使用双指针法。考虑构建一个指针,此指针需要有以下性质:此指针和 slow 一起向前走 a 步后,两者在入口节点重合。那么从哪里走到入口节点需要 a 步?答案是链表头节点head

双指针第二次相遇:

  • 令 fast 重新指向链表头部节点。此时 f=0,s=nb
  • slowfast 同时每轮向前走 1 步。
  • fast 指针走到 f=a 步时,slow 指针走到 s=a+nb 步。此时两指针重合,并同时指向链表环入口,返回 slow 指向的节点即可。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *fastNode = head, *slowNode = head;
        while (true) {
            //先判断fastNode本身和其下一个节点是否为空
            if (fastNode == nullptr || fastNode->next == nullptr) return nullptr;
            //如果fastNode->next为空,则这里会报空指针异常
            fastNode = fastNode->next->next;
            slowNode = slowNode->next;
            // 如果两节点相等,说明相遇,则退出循环
            if (fastNode == slowNode) break;
        }
        // 已经相遇,说明是环。则fastNode指向头节点,再移动a步,则两节点相遇点必为环的起始点。
        fastNode = head;
        while (slowNode != fastNode) {
            fastNode = fastNode->next;
            slowNode = slowNode->next;
        }
        return fastNode;
    }
};
  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值