题目和思路:
一、24. 两两交换链表中的节点
方法一(头递归):先复习一下昨天思考一下午的递归内容,1分钟搞定!
基本情况是链表head为空或者只有head一个节点,不用交换,直接return head。递归实现部分,先存一下head.next,这一步很重要,因为最后这就是头节点。head.next之后的部分递归完成了两两交换,返回了一个new_head,那么现在要做的就是把head连到new_head上,temp连到head上,最后返回temp作为new_head即可。代码如下:
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
#基本情况
if not head or not head.next:
return head
#递归实现
temp = head.next
new_head = self.swapPairs(head.next.next)
head.next = new_head
temp.next = head
return temp
方法二(迭代):双指针+虚拟头节点实现
跟代码随想录的步骤二和步骤三是反过来的但是也AC了~
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head = ListNode(next = head)
cur = dummy_head
while cur.next and cur.next.next:
temp = cur.next
cur.next = temp.next
temp.next = temp.next.next
cur.next.next = temp
cur = temp
return dummy_head.next
debug犯了很蠢的逻辑错误,写成了while not cur.next and not cur.next.next,while循环在条件为真的情况下才会工作,属于是写递归写傻了。
方法三(尾递归):复习一下昨天的内容,先操作再递归。
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
#基本情况
if not head or not head.next:
return head
#递归实现,先操作,再递归
cur = head
temp = head.next
temp1 = temp.next
temp.next = cur
cur.next = self.swapPairs(temp1)
return temp
抽象。
二、19.删除链表的倒数第N个节点
快慢双指针+虚拟头节点:快指针先走n+1步,随后双指针一起走直到快指针到头。这时快指针位置在sz+1(假设虚拟头节点是位置0),慢指针位置就在sz-n,这是目标节点前一个节点,也就是删除慢指针的next节点。代码如下:
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy_head = ListNode(next = head)
fast = slow = dummy_head
for _ in range(n + 1): fast = fast.next
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy_head.next
三、面试题 02.07. 链表相交
方法一:快慢指针,对两个head分别建立快慢指针,快指针先动,直到其中一个快指针到链表尾部,这时让另一个慢指针跟着快指针动,直到快指针到链表尾,这时两个慢指针一起动,如果指向节点相等,返回慢指针。
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
slowA = fastA = headA
slowB = fastB = headB
while fastA and fastB:
fastA = fastA.next
fastB = fastB.next
if not fastA:
while fastB:
fastB = fastB.next
slowB = slowB.next
else:
while fastA:
fastA = fastA.next
slowA = slowA.next
while slowA != slowB:
slowA = slowA.next
slowB = slowB.next
return slowA
本方法和随想录的方法其实本质相同,都是出发点减去链表长度差值让指针同步。就不写代码随想录上的方法了。
方法二:等比例法,相当于建了两个长度相等的环,双指针同时同速向前移动,遇到链表尾跳到对方的链表头,假如有交点,第二圈必定相遇,否则同时变成None。
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if not headA or not headB:
return None
curA = headA
curB = headB
while curA != curB:
curA = curA.next if curA else headB
curB = curB.next if curB else headA
return curA
四:142.环形链表II
这题n年前和室友讨论过,印象很深刻,纯靠记忆写。总体分成两步,检测是否有环,如果有,找入口。
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return None
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
fast = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
return None
-
检测环:快慢指针的方法能有效检测环的存在。因为如果有环,快指针最终会从后面追上慢指针。
-
找到环的入口:当快慢指针在环内相遇时,假设从头节点到环入口的距离是
D
,环入口到相遇点的距离是S
,环的长度是C
。此时,慢指针走了D + S
的距离,快指针走了D + S + nC
的距离(其中n
是快指针在环内多走的圈数)。因为快指针走的距离是慢指针的两倍,所以有2(D + S) = D + S + nC
,从而D = nC - S
。这意味着从头节点到环入口的距离等于从相遇点绕环n
圈后到达环入口的距离。因此,当一个指针从头节点出发,另一个指针从相遇点出发,它们以相同的速度移动时,将在环入口相遇。
今日总结:
复习了昨天的递归内容,很容易写出来了,有点欣慰,至少说明跟着刷题是十分有用的。
快慢指针对于没有接触过的而言有点难想出来,链表相交代码随想录题解里补环的方法让人眼前一亮,直接就是一个学。环形链表想明白还是挺容易理解的。