两两交换链表中的节点
这道题最好还是用虚拟头节点法,因为两两交换的时候头节点的位置也要变。不用虚拟头节点,需要单独维护头节点(head)的位置
# 正确解法
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head=ListNode(next=head)
current=dummy_head
while True:
if current==None or current.next==None or current.next.next==None:
break
else:
tmp1=current.next
tmp2=current.next.next.next
current.next=current.next.next
current.next.next=tmp1
current.next.next.next=tmp2
current=current.next.next
return dummy_head.next
这题需要画图,不然搞不清楚交换的顺序,还有谁的地址需要缓存。交换顺序具体可以参考代码随想录的文档。
删除链表的倒数第N个节点
反复向后试探
# 我的思路
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy_head=ListNode(next=head)
current=dummy_head
judge=current
while True:
for _ in range(n+1):
judge=judge.next
if judge==None:
break
current=current.next
judge=current
current.next=current.next.next
return dummy_head.next
我的思路是反复向后试探:judge往后移动n步,如果juege为None,则说明current已经到了倒数第n个节点了。因为需要删除倒数第N个节点,所以我需要current位于倒数第n+1个节点。
这种做法没错,但是时间复杂度较高,最坏的情况是o(L^2)。利用快慢指针的思想,可以把复杂度降低到o(L)
快慢指针
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy_head=ListNode(next=head)
current=dummy_head
judge=current
for _ in range(n+1):
judge=judge.next
while judge!=None:
current=current.next
judge=judge.next
current.next=current.next.next
return dummy_head.next
judge 初始化为 current 向后 n+1 个节点。judge 和 current 一起向后移动,当 juege 移动到 None 时,current 刚好指在倒数第 n+1 个节点上
链表相交
暴力循环
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
currentA=headA
currentB=headB
while(currentA!=None):
while(currentB!=None):
if(currentA==currentB):
return currentA
else:
currentB=currentB.next
currentA=currentA.next
currentB=headB
return None
找链表相交的点,其实就是找地址相等的点。最简单的方法就是双重循环来做,时间复杂度最坏的情况是o(mn),m是A链表的长度,n是B链表的长度。力扣有时间限制,不让这么做。
双指针解法
这个解法很巧妙,这道题有一个难点就是A,B的非公共部分的链表长度可能不一致,如果两个指针一起往后移动的话,没法同时到达交点。但是A走完走B,B走完走A,最后就可以让两个指针同时到达交点的位置
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
currentA=headA
currentB=headB
while(currentA!=currentB):
currentA=headB if currentA==None else currentA.next
currentB=headA if currentB==None else currentB.next
return currentA
环形链表
这道题有难度,自己想是很难想出来的,需要把解法作为经验积累
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head==None or head.next==None: #1. 链表是单节点或者空节点,一定没有环 2. 先判断head是否为空,再判断head.next是否为空,否则空列表没法运行head.next
return None
fast=head
slow=head
loop_exist=False
while fast!=None and fast.next!=None : #需要fast和fast.next都不为空,才能访问fast.next.next
fast=fast.next.next
slow=slow.next
if(fast==slow):
loop_exist=True
break
if loop_exist==False:
return None
else:
find=head
while(find!=slow):
find=find.next
slow=slow.next
return find
主要是两步:
- 判断是否存在循环(利用快慢指针,fast每次向后移动两位,slow每次向后移动一位),如果存在环,则 fast 一定会追上 slow。这个类似于在操场跑步,快的总会追上慢的
- 找到环的入口:find指针从链表的头节点向后,slow从fast,slow相遇的点向后移动,fine和slow相遇的点就是入口。具体的推理过程可以看代码随想录