代码随想录算法训练营第4天 | 24. 两两交换链表中的节点 / 19.删除链表的倒数第N个节点 / 面试题 02.07. 链表相交 / 142.环形链表II
•
算法结构
目录
- 链表
- 算法详解
-
- 24. 两两交换链表中的节点
-
- (1) 易错点
- (2) 思路
- (3) 代码
- 19.删除链表的倒数第N个节点
-
- (1) 易错点
- (2) 思路
- (3) 代码
- 面试题 02.07. 链表相交
-
- (1) 易错点
- (2) 思路
- (3) 代码
- 142.环形链表II
-
- (1) 关键点
- (2) 思路
- (3) 代码
- 参考资料
链表
链表:地址非连续,靠指针相互联系。
注意:具体的地址分散情况依据设定不同。
算法详解
24. 两两交换链表中的节点
(1) 易错点
- 虚拟头结点使用:由于头结点并没有真正的前置节点,交换时假设不采用虚拟头结点则需要对头结点单独处理。
- 两个节点交换涉及到四个节点:在交换A-B这段链表切片上,实现AB的交换,还涉及到A的前置节点和B的后置节点。
- 循环条件:当cur执行到cur.next.nexr=None时,达到链表尾部,此时跳出循环,另外,假设cur.next已经为None,则无法执行cur.next.next,故需添加cur.next的条件。
(2) 思路
为不单独处理头结点的交换,创建虚拟头结点指向head。如上文所说,AB的交换涉及到四个节点,A节点可以用指pre针访问,而A的前置节点无法访问;B节点用pre.next访问,而当交换发生时,B指向A,B节点原本的后置节点无法访问,因此我们需要一个指针cur来访问A的前置节点,而指针tail访问B的后置节点。
- 创建虚拟头结点dummy_head,cur指向dummy_head。
- 初始化pre和tail的位置。
- 交换:①将A的前置节点指向B;②将B指向A;③将A指向B的后置节点。如下图所示:

(3) 代码
class Solution: def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: dummy_head = ListNode(next=head) cur = dummy_head # 当cur.next.next.next == None 时,到链表尾部 while cur.next and cur.next.next: pre = cur.next tail = cur.next.next.next cur.next = cur.next.next cur.next.next = pre pre.next = tail # 相邻两个元素交换,因此指针移动两位 cur = cur.next.next return dummy_head.next
19.删除链表的倒数第N个节点
(1) 易错点
- 循环条件:当tail.next为None时,代表tail节点到链表结尾,此时跳出循环。
- 单链表无法反向访问:和删除链表的第n个元素类似,一开始思考可以先遍历到链表尾部然后反向找第n个节点,然而单链表无法反向访问,故行不通。
(2) 思路
设置两个间隔为n的指针从head遍历到链表尾,此时前一个指针所指的位置就是待删除的节点。
- 设置tail节点走到第n个节点;
- cur从head出发,tail从第n个节点出发遍历;
- 当tail访问到链表尾节点时跳出循环;
- 删除cur.next元素。
- 具体思路如下图所示:

(3) 代码
class Solution: def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: # 创建虚拟头结点避免删除头结点时要另外执行 dummy_head = ListNode(next=head) cur = dummy_head tail = dummy_head # 在cur和tail之间创建间隔n for _ in range(n): tail = tail.next # 将tail移动至链表尾部此时cur.next即为待删除元素 while tail.next: cur = cur.next tail = tail.next cur.next = cur.next.next return dummy_head.next
面试题 02.07. 链表相交
(1) 易错点
- 相交比较的是指针不是节点值。
- 尾部对齐之后较长的链表应该从链表长度之差处开始遍历并比较。
(2) 思路
将两条链表尾部对齐之后,假设有相交,则从尾节点往head方向会出现相等的节点。
- 分别遍历链表A和B得到长度lenA和LenB;
- 为简化,设置链表A为较长的一条,若不满足则lenA、lenB和curA、curB交换;
- 计算长度差lenAa-lenB,将curA遍历到和curB对齐;
- 同时移动curA和curB直至相等,此时找到链表交点;
- 若遍历到链表尾部也不相等则链表不相交。
- 具体思路如下图所示:

(3) 代码
class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: cur_a, len_a = headA, 0 cur_b, len_b = headB, 0 # 将a和b都先移动到链表尾部得到链表长度 while cur_a: cur_a = cur_a.next len_a += 1 while cur_b: cur_b = cur_b.next len_b += 1 # 始终让cur_a指向较长的链表 cur_a, cur_b = headA, headB if len_b > len_a: cur_a, cur_b = cur_b, cur_a len_a, len_b = len_b, len_a # 计算两条链之间的长度差 len_ab = len_a -len_b # 将两条链表尾部对齐 for _ in range(len_ab): cur_a = cur_a.next # 将两条链同时移动并比较指针 while cur_a and cur_b: if cur_a == cur_b: return cur_a cur_a = cur_a.next cur_b = cur_b.next return None
142.环形链表II
(1) 关键点
- 链表是否有环判断:若slow和fast相遇则链表有环;
- slow和fast相遇点计算。
注意:至少一圈内slow和fast会相遇。
(2) 思路
当链表中存在环时,每次移动一个节点的slow指针和每次移动两个节点的fast指针会在环中相遇。通过简单的数学计算(如图)发现,当slow和fast相遇时,此时fast位置不变,节点slow从head和fast一起同时移动,且每次只移动一个节点,当slow和fast再次相遇时为环的入口。
- 初始化slow、fast并移动,slow每次移动一个节点,fast移动两个节点;
- 第一次相遇:fast首先进入环,移动一段时间后和后进入环的slow相遇,即为图中绿色节点;
- 第二次相遇:fast从第一次相遇点出发,slow从head出发,每次均只移动一个节点;当第二次相遇时为环入口,即图中黄色节点。
!

(3) 代码
class Solution: def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: slow, fast = head, head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: slow = head while not(slow == fast): slow = slow.next fast = fast.next return slow return None
参考资料
(1)24. 两两交换链表中的节点
(2)19.删除链表的倒数第N个节点
(3)面试题 02.07. 链表相交
(4) 142.环形链表II
本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/568c7b8169.html
