题目
如果一个链表中包含环,从链表头节点开始顺着next指针方向进入环的第1个节点为环入口节点。
现在已知头结点 head ,求定位入口节点。
思路:
差速法 - 快慢指针相遇时扣n圈,调整起始位置可令二者刚好在入口节点相遇。
步骤:
1.定位“相遇点” - 快慢指针同时从头出发,定位扣圈相遇点(这个动作也可用于判断链表是否有环,并返回环内一个节点,很有用!)
2.P1从头节点出发,P2从相遇点出发,再相遇时即是入口节点
样例(来自剑指Offer第四章Q22):
已知图中链表头结点指针,返回内容应为入口节点3的指针
先把例题链表创建出来:
'''定义链表节点'''
class ListNode():
def __init__(self, item):
self.item = item
self.next = None
'''创建样例链表'''
arr = [1,2,3,4,5,6]
n = len(arr)
head = ListNode(arr[0])
cur = head
for i in range(1,n):
node = ListNode(arr[i])
cur.next = node
cur = cur.next
'''连上圆环'''
tail = head
for i in range(0,5):
tail = tail.next
entry = head
for i in range(0,2):
entry = entry.next
tail.next = entry
创建好以后,检查一下链表内容,连得对不对
'''检验环形链表'''
arr_check = []
cur = head
tag = 0
while(cur != None)&(tag<15):
arr_check.append(cur.item)
cur = cur.next
tag+=1
print(arr_check)
结果输出如下(从环的位置开始循环输出,就是1 2 3 4 5 6 -> 3 4 5 6 -> 3 4 5 6 -> 3...):
[1, 2, 3, 4, 5, 6, 3, 4, 5, 6, 3, 4, 5, 6, 3]
实现查找
解决方案类包含两个函数:
- locateMeetNode() 负责找“相遇节点”顺便判断链表是否有环
- findEntry() 负责利用相遇节点推出入口节点
class Solution():
'''Step 1 locate meet node - fast vs slow'''
def locateMeetNode(self, head: ListNode) -> ListNode:
if (head == None)|(head.next == None):
print('List chain not exist or contains 1 node only.')
return None
else:
slow = head.next
fast = slow.next
while(fast != None)&(slow != None):
if(fast == slow):
return slow
slow = slow.next
fast = fast.next
if(fast != None):
fast = fast.next
'''if the loop ends without returning anything, then there is no circle in list chain'''
print('This list chain contains no circle.')
return None
'''Step 2 start from meet node - meet again'''
def findEntry(self, head: ListNode) -> ListNode:
p1 = head
p2 = self.locateMeetNode(head)
if(p2 == None):
print('No entry because there is no circle.')
return None
while(p1 != p2):
p1 = p1.next
p2 = p2.next
return p1
检测代码:
1.检测相遇点是否正确,按本题链表,应该是5
s1 = Solution()
meet = s1.locateMeetNode(head)
meet.item
结果输出:5
2.检测入口节点是否正确,按本题链表,应该是3
entry = s1.findEntry(h1)
entry.item
结果输出:3
反思:卡住的地方
1.解决方案类的实例化 s1=Solution() 查找原因15分钟,尝试重构函数15分钟,共计约半小时
2.使用双指针方法必须考虑的极端情况:head==None, head.next==None 直接返回None
3.双指针loop循环控制顺序:先比较指针,再走步,再快针查空+1步
4.粗心问题:由于typo把快指针起点写错,导致debug 10分钟
在后续实践中要持续夯实基础、并预防粗心问题,从而减少debug时间。