24. 两两交换链表中的节点
一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
# 设置虚拟节点试图统一链表里的操作,虚拟节点的设置方法是?
dummy_head=ListNode(next=head)
current=dummy_head
遇到了问题,想知道更新的操作,
在当前节点指向的需要交换的节点,current.next和current.next.next都存在的时候,
交换后第一个节点要指向第二个节点的next,current要指向第二个节点,第二个节点要指向起一个节点
这个顺序怎么来,
ListNode temp = current.next
current.next=current.next.next
current.next.next=current.next.next.next 话说存在吗?指到空怎么办?
current.next.next.next=current.next (变化了,需要定义一个临时记录的节点temp)
然后移动current到下一对需要交换的节点对的前一个节点
依旧是错误的代码:
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 设置虚拟节点
dummy_head=ListNode(next=head)
current=dummy_head # current指向虚拟头节点
# 当 current.next 和 current.next.next 都存在时,进行交换
while current.next and current.next.next :
ListNode temp = current.next
ListNode temp1 = current.next.next.next
current.next=current.next.next
current.next.next=temp #话说存在吗?指到空怎么办?
temp.next=temp1 #变化了,需要定义一个临时记录的节点temp
current=temp
return dummy_head.next
要注意原来第一个节点dummy到节点1,节点2到3的指向断了,导致节点1、节点3需要保存。
但是一直报错,完全不理解,哪位友友知道的话麻烦留言QAQ
有同志告诉我current.next=current.next.next时,那么temp也被修改了(因为他等于current.next)
current 时,应该更新为 current.next.next,因为当前 current.next 已经被交换到了后面,我们需要移动到下一对需要交换的节点对的前一个节点。temp是会修改的,所以不应该使用。
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 # current指向虚拟头节点
# 当 current.next 和 current.next.next 都存在时,进行交换
while current.next and current.next.next:
first = current.next # 第一个节点
second = current.next.next # 第二个节点
# 更新指针以交换节点
first.next = second.next # 第一个节点的next指向第二个节点的next
current.next = second # current的next指向第二个节点
second.next = first # 第二个节点的next指向第一个节点
# 移动current到下一对需要交换的节点对的前一个节点
current = current.next.next
return dummy_head.next
19.删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
想到了双指针,先向前移动n-1步,然后两个指针同时移动,直到先行的指针到达链表的末尾
话说链表的长度可以计算吗,关于链表的长度,确实需要确保链表的长度大于或等于n,否则删除倒数第n个节点就没有意义,但是这个要遍历才能求的长度?好像
错误记录:
1、创建了新的ListNode对象,并传递了head
作为参数。
right=ListNode(head)
left=ListNode(head)
2、忘了考虑删除的是头节点的情况,如果不使用虚拟节点,需要特别处理头节点的情况。在初始化left
和right
指针时,直接将它们指向链表的头节点head
。然而,当需要删除的是头节点时,这会导致一些问题,因为left
指针会指向None
,在尝试删除节点时会出现错误。所以要初始节点
综上两点,初始化:
# 使用虚拟节点简化头节点的删除情况
dummy_head = ListNode(0)
dummy_head.next = head
right = dummy_head
left = dummy_head
每次都在想不到的地方报错,这次又是为了啥?修改了成了 if right!=None:
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# 使用虚拟节点简化头节点的删除情况
dummy_head = ListNode(next=head)
right = dummy_head
left = dummy_head
# 先让right指针向前移动n+1步
for i in range(1+n):
if right!=None:
right=right.next
while right:
right=right.next
left=left.next
left.next=left.next.next
return dummy_head.next
面试题 02.07. 链表相交
两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
说实话,看图之前,我对链表相交这个概念都很困惑,可以部分相交吗?后来想想,链表移动会指向下一个,相交就是一定在某个点指向同一个点,然后相交到末尾,直到None。
这个可以怎么做?应该是尾部对齐后才好比较,话说求链表长度还得先遍历啊。
这题需要虚拟节点吗?如果第一个节点就相交要考虑吗?不过不删除总感觉虚拟节点用处不大。
写的初版,超出时间限制
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 从某个点开始相交,直到末尾,如果相交,从尾部开始倒数第n个开始相交,n>=1
# 遍历分别求链表长度
curA, curB = headA, headB
nA,nB = 0,0
while curA:
curA=curA.next
nA+=1
while curB:
curB=curB.next
nB+=1
# 重新指向开头
curA,curB = headA,headB
# 得到长度差
diff = abs(nA-nB)
#-------错误的--------
if nA<nB:
# B先向前移动(nB-nA)个
for i in range(diff):
curB=curB.next
while curA and curB:
if curA == curB:
return curA
curA = curA.next
curB = curB.next
else:
# A先向前移动(nA-nB)个
for i in range(diff):
curA=curA.next
while curA and curB:
if curA == curB:
return curA
curA = curA.next
curB = curB.next
#-------修正的--------
if nA<nB:
# B先向前移动(nB-nA)个
for i in range(diff):
curB=curB.next
while curA and curB:
if curA == curB:
return curA
curA = curA.next
curB = curB.next
else:
# A先向前移动(nA-nB)个
for i in range(diff):
curA=curA.next
while curA and curB:
if curA == curB:
return curA
curA = curA.next
curB = curB.next
return None
这里面正确的实现应该先求出两个链表的长度差,并让长的链表先走这个长度差,然后再同时遍历两个链表,直到找到交点或到达链表末尾。
在原始代码中,for
循环用于让较长的链表先移动diff
步,但while
循环被错误地嵌套在for
循环内部。这意味着每次for
循环迭代一次,内层的while
循环就会完全执行一次,这并不是我们的目的。我们的目的是让长链表先移动diff
步后,再同时遍历两个链表以寻找交点。
修改下也可以成功,但是是有点不清晰,改进代码如下:
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 初始化两个指针
curA, curB = headA, headB
# 遍历分别求链表长度
nA, nB = 0, 0
while curA:
curA = curA.next
nA += 1
while curB:
curB = curB.next
nB += 1
# 重新初始化指针指向链表开头
curA, curB = headA, headB
# 得到长度差
diff = abs(nA - nB)
# 让长的链表先走diff步
if nA > nB:
for _ in range(diff):
curA = curA.next
else:
for _ in range(diff):
curB = curB.next
# 此时两个链表剩余部分长度相同,或者其中一个链表已经遍历完
# 同时遍历两个链表,直到找到交点或到达链表末尾
while curA and curB:
if curA == curB:
return curA
curA = curA.next
curB = curB.next
# 如果没有交点,返回None
return None
话说还可以这样,交换
if lenA > lenB: # 让curB为最长链表的头,lenB为其长度
curA, curB = curB, curA
lenA, lenB = lenB, lenA
for _ in range(lenB - lenA): # 让curA和curB在同一起点上(末尾位置对齐)
curB = curB.next
142.环形链表II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
Ps.不理解这里想说啥:评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。输入里也没有啊?
环的话,使用快慢指针来判断链表是否有环,并求解环的入口节点。使用两个指针,一个慢指针每次移动一步,一个快指针每次移动两步。如果链表中存在环,那么两个指针最终会相遇在环中的某个节点上。
假设环的入口到链表头部的距离为x
,环内快慢指针相遇时慢指针移动的距离为y
,环内从相遇的地点到环的入口的距离为z
。快指针的速度是慢指针的两倍。
在快慢指针相遇时,快指针走过的距离是慢指针的两倍。因此,我们有:
快指针走过的距离 = 慢指针走过的距离 × 2
(x + y) × 2 = x + y + n × 环的周长,其中n是快指针在环内绕的圈数。
环的周长可以表示为y + z
(从快慢指针相遇点到环的入口,再加上从环的入口到快慢指针相遇点)。由于慢指针在相遇时已经走了y
的距离,所以快指针绕了(y + z) × n
的距离。
将环的周长代入上面的等式中,我们得到:
(x + y) × 2 = x + y + n × (y + z)
我们让慢指针回到头节点,并将快慢指针的速度都调整为每次一步
慢指针从头节点开始,到达环的起始节点需要走 x
步。快指针从相遇点开始,它首先需要走 z
步到达环的起始节点,然后它可能在环里多走几圈,但每多走一圈都会再次回到环的起始节点。
又x=(n-1)x(y+z)+z,所以,这种情况下(我们让慢指针回到头节点,并将快慢指针的速度都调整为每次一步),两个指针将在入口处相遇。
数学分析结束了,代码实现:
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return None # 如果链表为空或只有一个节点,则一定没有环
fast,slow=head,head
# 快慢指针第一次相遇
while fast and fast.next:
fast=fast.next.next # 话说要注意fast.next是否存在
slow=slow.next
if fast==slow: # 相遇则跳出循环
break
# 没有环
if fast==None:
return None
#
slow=head
while slow!=fast:
slow=slow.next
fast=fast.next
return slow