24.两两交换链表中的节点
题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
题目链接:24. 两两交换链表中的节点
卡哥的视频讲解:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点
题目思考:定义一个虚拟头节点,对该节点后的两个节点进行操作,注意判断可循环条件以及定义变量来暂时储存节点。
交换过程如图:
代码示例:
代码逻辑详解:
-
首先创建一个虚拟头节点
dumyhead
,值为 -1,并将其指向链表的头节点head
。这样做是为了处理头节点的特殊情况。 -
创建指针
cur
指向虚拟头节点dumyhead
,用于遍历链表。 -
创建临时指针
temp
用于指向待交换节点的前一个节点,即第一个要交换的节点的前一个节点。 -
创建临时指针
temp1
用于指向待交换节点的后一个节点的后一个节点,即第二个要交换的节点的后一个节点。 -
创建两个指针
first
和second
分别指向待交换的两个节点。 -
进入循环,条件是
cur.next
和cur.next.next
都不为 null,即链表中至少有两个节点。这样做是为了保证交换的两个节点是相邻的。 -
在循环中,首先保存好要交换的两个节点及其前后节点的位置,以便后续指针操作。
-
将
cur.next
指向第二个节点second
,即将第二个节点提前。 -
将
second.next
指向第一个节点first
,完成交换。 -
将
first.next
指向之前保存的temp1
,即将第一个节点的下一个节点指向原来第二个节点的后一个节点。 -
将
cur
指向交换后的第一个节点,为下一轮交换做准备。 -
循环结束后,返回虚拟头节点的下一个节点,即交换后的链表的头节点。
这样,整个链表中相邻节点的交换就完成了。
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是为了让慢指针指向被删除节点的前一个),再让快慢指针同时走,当快指针指向空时,慢指针就走到被删除节点的前一个了。(如下图)
代码示例:
代码详细逻辑:
-
首先创建一个虚拟头节点
dummyhead
,值为 -1,并将其指向链表的头节点head
。这样做是为了处理删除头节点的特殊情况。 -
创建两个指针
fast
和slow
,都指向虚拟头节点dummyhead
。fast
指针先向前移动 n+1 步,使其与slow
指针相隔 n 个节点。 -
进入循环,条件是
fast
指针不为 null。在循环中,fast
指针和slow
指针同时向前移动,直到fast
指针指向 null,即到达链表的末尾。 -
循环结束后,
slow
指针指向要删除节点的前一个节点,而fast
指针指向要删除节点的后一个节点。 -
将要删除节点的前一个节点
slow
的next
指针指向要删除节点的下一个节点,从而将要删除的节点从链表中删除。 -
返回虚拟头节点的下一个节点,即删除节点后的链表的头节点。
这样,就完成了删除链表中倒数第 n 个节点的操作。
leetcode提交记录:
小tips:
1.一定要记得是让快指针走n+1步,这样慢指针才可以指向被删除节点的前一位,方便操作
2.定义快慢指针时,一定要定义在虚拟头节点的位置,如果在 head
的位置初始化快慢指针,那么在处理删除头节点的情况时就会变得复杂。因为如果要删除的是头节点,需要同时更新头节点 head
的位置,而且要处理头节点的特殊情况可能会引入更多的边界条件。
通过使用虚拟头节点 dummyhead
,可以避免对头节点的特殊处理。虚拟头节点实际上是一个哨兵节点,它位于链表的头部,但不存储任何有效数据。这样,在处理链表的逻辑中,就可以统一对待头节点和其他节点,简化了代码逻辑,提高了代码的可读性和可维护性。
链表相交:
题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
题目链接:面试02.07链表相交
题目思考:定义两个指针,分别遍历两个链表,如果指针有相同的,则说明有相交的节点。
代码示例:
代码逻辑详解:
-
首先,创建两个指针
curA
和curB
分别指向输入的两个链表的头节点headA
和headB
。 -
创建两个变量
lenA
和lenB
,分别用于记录两个链表的长度。通过遍历链表,分别计算出两个链表的长度。 -
重新将指针
curA
和curB
指向链表的头节点,以便在接下来的操作中遍历链表。 -
如果链表 B 的长度大于链表 A 的长度,交换
lenA
和lenB
的值,同时交换指针curA
和curB
的指向,使得curA
指向较长的链表,curB
指向较短的链表。 -
计算出两个链表的长度差,即
gap
,让较长的链表的指针curA
先向后移动gap
步。 -
然后,同时移动指针
curA
和curB
,直到找到两个指针相等的节点,即找到了交点,返回该节点。 -
如果在遍历完链表后仍未找到交点,即
curA
和curB
都为 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,也就是将两个指针,一个定义在头节点,一个定义在相遇节点,当两个节点再次相遇时,也就是环形链表的入口点。
代码示例:
代码逻辑详解:
-
首先,我们初始化两个指针
slow
和fast
,它们都指向链表的头节点head
。 -
在循环中,我们每次让
slow
指针向后移动一步,fast
指针向后移动两步。这样,如果链表中存在环,那么快指针fast
一定会追上慢指针slow
。 -
在每次循环迭代中,我们先检查
fast
指针和fast.next
指针是否为空,以确保链表中没有null
指针,从而避免空指针异常。 -
如果快慢指针相遇,即
slow == fast
,说明链表中存在环。 -
一旦确定存在环,我们再次使用两个指针
index1
和index2
,分别从相遇点和链表的头节点出发,每次向后移动一步,直到它们相遇。 -
当
index1
和index2
相遇时,它们相遇的节点就是环的起始节点。 -
最后,我们返回环的起始节点。
这样,就完成了检测并返回链表中的环的起始节点的操作。
leetcode提交记录:
小tips:
循环条件 fast != null && fast.next != null
是为了确保在使用快慢指针法检测链表中的环时,不会遇到空指针异常,并且可以保证快指针 fast
不会在链表的末尾停止,从而正确检测到环的存在。
让我们来考虑一下两种情况:
-
如果只使用
fast != null && fast.next != null
作为循环条件:-
在快慢指针法中,快指针
fast
每次移动两步,慢指针slow
每次移动一步。如果链表中存在环,那么快指针fast
一定会追上慢指针slow
。 -
如果链表不存在环,那么快指针
fast
最终会到达链表的末尾,即fast
指向null
,循环条件fast != null && fast.next != null
就不满足,循环结束。
-
-
如果使用
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
,而且也能够处理链表不存在环的情况,不会导致空指针异常。