代码随想录算法训练营Day4 | 24.两两交换链表中的节点、19删除链表中的第N个节点、链表相交、142.环形链表

文章介绍了四个与链表相关的LeetCode题目,包括两两交换链表节点、删除链表倒数第N个节点、查找链表相交点以及检测环形链表的入口。通过定义虚拟头节点和使用快慢指针策略,解决了这些问题的关键逻辑和边界条件。
摘要由CSDN通过智能技术生成

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

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

题目链接:24. 两两交换链表中的节点

卡哥的视频讲解:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点

题目思考:定义一个虚拟头节点,对该节点后的两个节点进行操作,注意判断可循环条件以及定义变量来暂时储存节点。

交换过程如图:

代码示例:

代码逻辑详解:

  1. 首先创建一个虚拟头节点 dumyhead,值为 -1,并将其指向链表的头节点 head。这样做是为了处理头节点的特殊情况。

  2. 创建指针 cur 指向虚拟头节点 dumyhead,用于遍历链表。

  3. 创建临时指针 temp 用于指向待交换节点的前一个节点,即第一个要交换的节点的前一个节点。

  4. 创建临时指针 temp1 用于指向待交换节点的后一个节点的后一个节点,即第二个要交换的节点的后一个节点。

  5. 创建两个指针 firstsecond 分别指向待交换的两个节点。

  6. 进入循环,条件是 cur.nextcur.next.next 都不为 null,即链表中至少有两个节点。这样做是为了保证交换的两个节点是相邻的。

  7. 在循环中,首先保存好要交换的两个节点及其前后节点的位置,以便后续指针操作。

  8. cur.next 指向第二个节点 second,即将第二个节点提前。

  9. second.next 指向第一个节点 first,完成交换。

  10. first.next 指向之前保存的 temp1,即将第一个节点的下一个节点指向原来第二个节点的后一个节点。

  11. cur 指向交换后的第一个节点,为下一轮交换做准备。

  12. 循环结束后,返回虚拟头节点的下一个节点,即交换后的链表的头节点。

这样,整个链表中相邻节点的交换就完成了。

leetcode提交记录:

小tips:

1.先把节点交给指针之后,再进行交换,别忘了设置两个temp值

2.注意循环的条件,这里巧妙地将链表个数为偶数和奇数的情况都囊括了,当链表个数为偶数时,如果cur的下一个为空,则停止循环,如果链表个数为奇数时,则还需要同时满足cur下一个的下一个为空才会停止循环,这也是为什么只能用和而不能用或。注意!!

                     cur.next != null&&cur.next.next != null
这两个条件的顺序不能调换,否则会报空指针异常的错误。

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

题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

题目链接:19. 删除链表的倒数第 N 个结点

卡哥的视频链接:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点

题目思考:要找到删除的节点,就要知道它的上一个节点是什么,我开始的想法是把链表反转过来,直接删除第n个节点,但是这个操作就复杂了,可以直接使用快慢指针。假如要删除倒数第n个节点,则让快指针先走n+1步(+1是为了让慢指针指向被删除节点的前一个),再让快慢指针同时走,当快指针指向空时,慢指针就走到被删除节点的前一个了。(如下图)

代码示例:

代码详细逻辑:

  1. 首先创建一个虚拟头节点 dummyhead,值为 -1,并将其指向链表的头节点 head。这样做是为了处理删除头节点的特殊情况。

  2. 创建两个指针 fastslow,都指向虚拟头节点 dummyheadfast 指针先向前移动 n+1 步,使其与 slow 指针相隔 n 个节点。

  3. 进入循环,条件是 fast 指针不为 null。在循环中,fast 指针和 slow 指针同时向前移动,直到 fast 指针指向 null,即到达链表的末尾。

  4. 循环结束后,slow 指针指向要删除节点的前一个节点,而 fast 指针指向要删除节点的后一个节点。

  5. 将要删除节点的前一个节点 slownext 指针指向要删除节点的下一个节点,从而将要删除的节点从链表中删除。

  6. 返回虚拟头节点的下一个节点,即删除节点后的链表的头节点。

这样,就完成了删除链表中倒数第 n 个节点的操作。

leetcode提交记录:

小tips:

1.一定要记得是让快指针走n+1步,这样慢指针才可以指向被删除节点的前一位,方便操作

2.定义快慢指针时,一定要定义在虚拟头节点的位置,如果在 head 的位置初始化快慢指针,那么在处理删除头节点的情况时就会变得复杂。因为如果要删除的是头节点,需要同时更新头节点 head 的位置,而且要处理头节点的特殊情况可能会引入更多的边界条件。

通过使用虚拟头节点 dummyhead,可以避免对头节点的特殊处理。虚拟头节点实际上是一个哨兵节点,它位于链表的头部,但不存储任何有效数据。这样,在处理链表的逻辑中,就可以统一对待头节点和其他节点,简化了代码逻辑,提高了代码的可读性和可维护性。

链表相交:

题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

题目链接:面试02.07链表相交

题目思考:定义两个指针,分别遍历两个链表,如果指针有相同的,则说明有相交的节点。

代码示例:

代码逻辑详解:

  1. 首先,创建两个指针 curAcurB 分别指向输入的两个链表的头节点 headAheadB

  2. 创建两个变量 lenAlenB,分别用于记录两个链表的长度。通过遍历链表,分别计算出两个链表的长度。

  3. 重新将指针 curAcurB 指向链表的头节点,以便在接下来的操作中遍历链表。

  4. 如果链表 B 的长度大于链表 A 的长度,交换 lenAlenB 的值,同时交换指针 curAcurB 的指向,使得 curA 指向较长的链表,curB 指向较短的链表。

  5. 计算出两个链表的长度差,即 gap,让较长的链表的指针 curA 先向后移动 gap 步。

  6. 然后,同时移动指针 curAcurB,直到找到两个指针相等的节点,即找到了交点,返回该节点。

  7. 如果在遍历完链表后仍未找到交点,即 curAcurB 都为 null,则返回 null,表示两个链表没有交点。

这样,就完成了找到两个链表的交点的操作。

leetcode提交记录:

小tips:在比较两个链表的长度之前,记得重新赋值一遍噢!

142.环形链表

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

为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

题目链接:142. 环形链表 II

卡哥的视频讲解:​​​​​​把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II

题目思路:

首先判断是否有环,再去找到入口,定义快慢指针,当快指针进入环之后,慢指针也会进入环内,快指针会去追慢指针,当快慢指针相遇,就可以得出有环的结论。

判断是否有环:定义两个指针,让快指针每次走两步,慢指针每次走一步,当快指针走到环内部之后,慢指针才会进入环内,此时是快指针在追慢指针,但是对于慢指针来说,快指针是一步一步在追它的,所以存在环形结构 话,两个指针是一定会相遇的。

判断入口在哪:

如图,快指针追上慢指针时,快指针走过的距离为x+y+n(y+z),慢指针走过的距离为x+y,其中n为快指针走过n圈后追上慢指针。因为快指针每次都走两步,所以快指针走过的节点数是慢指针的两倍,也就是(x+y)*2 = x+y+n(y+z),因为要找的是入口值,也就是图中x的长度,最后化简可得到x = (n-1)(y+z)+z。

当快指针只走了一圈就追上慢指针时,带入式子可以得到x = z,也就是将两个指针,一个定义在头节点,一个定义在相遇节点,当两个节点再次相遇时,也就是环形链表的入口点。

代码示例:

代码逻辑详解:

  1. 首先,我们初始化两个指针 slowfast,它们都指向链表的头节点 head

  2. 在循环中,我们每次让 slow 指针向后移动一步,fast 指针向后移动两步。这样,如果链表中存在环,那么快指针 fast 一定会追上慢指针 slow

  3. 在每次循环迭代中,我们先检查 fast 指针和 fast.next 指针是否为空,以确保链表中没有 null 指针,从而避免空指针异常。

  4. 如果快慢指针相遇,即 slow == fast,说明链表中存在环。

  5. 一旦确定存在环,我们再次使用两个指针 index1index2,分别从相遇点和链表的头节点出发,每次向后移动一步,直到它们相遇。

  6. index1index2 相遇时,它们相遇的节点就是环的起始节点。

  7. 最后,我们返回环的起始节点。

这样,就完成了检测并返回链表中的环的起始节点的操作。

leetcode提交记录:

小tips:

循环条件 fast != null && fast.next != null 是为了确保在使用快慢指针法检测链表中的环时,不会遇到空指针异常,并且可以保证快指针 fast 不会在链表的末尾停止,从而正确检测到环的存在。

让我们来考虑一下两种情况:

  1. 如果只使用 fast != null && fast.next != null 作为循环条件:

    • 在快慢指针法中,快指针 fast 每次移动两步,慢指针 slow 每次移动一步。如果链表中存在环,那么快指针 fast 一定会追上慢指针 slow

    • 如果链表不存在环,那么快指针 fast 最终会到达链表的末尾,即 fast 指向 null,循环条件 fast != null && fast.next != null 就不满足,循环结束。

  2. 如果使用 fast != null && fast.next != null && fast.next.next != null 作为循环条件:

    • 在链表中存在环的情况下,快指针 fast 永远不会指向空节点,因为它会在环中循环移动,永远不会停止。

    • 但是,如果链表的长度很短,快指针 fast 在环中进行追赶的过程中可能会超出链表的长度,导致 fast.next.next 为null。这样,在检查循环条件时,快指针 fast 的移动将因 fast.next.next != null 的条件而提前结束,而不是检测到环的存在。

因此,使用 fast != null && fast.next != null 作为循环条件是更合适的选择,因为它能够保证在链表中存在环时,快指针 fast 能够正确地追赶慢指针 slow,而且也能够处理链表不存在环的情况,不会导致空指针异常。

  • 36
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值