1 解题思路
在单链表中存在环状结构大致可以画成以下样子:
可以看到node5结点的指针本应指向null,却指到了node2,使得链表中出现了一个环,有点类似循环单链表的结构。此时去遍历整个链表,会死循环。那如何找出这个环的入口呢?
- 分析 先在头结点设置两个快慢指针,快指针每次移动两个结点,而慢指针每次移动一个结点,那么根据操场跑圈的经验,跑的慢的指针必定会被跑得快的指针“套圈”,我们把环入口到他们两个相遇点的距离设为a,假设在相遇前,快指针已经跑了n圈,环的周长为C,头结点到环入口的距离为L,如图所示:
我们计算快指针经过的路程(根据快指针走的路程是慢指针的2倍),可以得到以下等式:
化简后得到:
这个化简后的等式可以得出以下结论:
- 从头结点到环入口的距离等于从环入口跑了n圈的长度减去环入口到相遇点的距离。
- 换句话说,如果让一个结点从头开始跑,另一个结点从相遇处开始跑,那么他们两个必定会在环入口相遇。
到这里,问题已经理清,接下来就要做以下两个步骤:
- 利用快慢指针找出两者相遇的结点
- 让一个指针从头结点开始跑,另一个从相遇处开始跑,返回两者相遇的结点
2 Python代码实现
2.1 构建单链表
# 结点
class Node(object):
def __init__(self, element):
self.element = element
self.next = None
# 带头节点的单链表
class linklist(object):
def __init__(self):
self.next = None
# 从尾部添加结点
def append(self, element):
node = Node(element)
if self.next == None:
self.next = node
else:
cur = self.next
while cur.next != None:
cur = cur.next
cur.next = node
# 打印链表
def traverse(self):
alist = []
cur = self.next
while cur != None:
alist.append(cur.element)
cur = cur.next
return alist
2.2 找到相遇结点
求解相对比较简单,保证慢结点一次移动一个单位,快结点一次移动两个单位:
def meet(node):
slow = node.next # 慢指针
fast = node.next.next # 快指针
while slow != fast: # 当指针指向的结点相同时停止循环
slow = slow.next
fast = fast.next.next
return slow #返回任意一个指针的结点皆可
2.3 找到环入口
设置两个指针,一个从头跑,一个从相遇的结点开始跑(调用上述函数):
def is_ring(linklist):
cur1 = linklist.next # 从头结点开始跑的指针
cur2 = meet(linklist.next) # 相遇的结点
while cur1 != cur2: # 两者相遇的结点即为环入口
cur1 = cur1.next
cur2 = cur2.next
return cur1
2.4 测试
创建链表,插入一些数据,并将尾结点指针指向第三个结点(10),以此构造出环结构:
# 创建列表
a = linklist()
# 插入数据
for i in [2, 5, 10, 98, 1, 3, 56]:
a.append(i)
# 构造环
cur = a.next
while cur.next != None:
cur = cur.next
cur.next = a.next.next.next
# 测试
b = is_ring(a)
b.element
> 10
可以看到最终返回了第三个结点为(10),问题顺利解决~