剑指offer系列-面试题23-链表中环的入口节点(python)

文章目录

  • 1.题目
  • 2.解题思路
    • 2.1 我的思路
    • 2.2 书中思路
      • 第一步,如何确定链表中是否有环呢?
      • 第二步,如何找到环的入口节点?
  • 3. 代码实现
    • 3.1 我的思路
  • 3.2 书中思路
  • 4.总结
  • 5.参考文献

1.题目

如果一个链表中包含环,如何找出环的入口节点?例如,在如图所示的链表中,环的入口节点是节点3。

1
2
3
4
5
6

2.解题思路

链表中有没有环?什么样的节点是环的入口节点呢?

2.1 我的思路

可不可以用一个list记录链表中已经遍历过的节点,第一个出现重复的节点,应该就是环的入口节点吧。是否正确?经过实践,这种思路是可行的。时间复杂度是O(n),空间复杂度S(n)。

2.2 书中思路

面试官若是要求空间复杂度小于S(n),那么应该怎么来实现呢?

第一步,如何确定链表中是否有环呢?

利用两个速度不同的指针,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走的慢的指针,那么链表中就有环,如果走的慢的指针走到了链表的末尾都还没有被走的快的指针追上,那么链表中没有环。

第二步,如何找到环的入口节点?

使用两个指针,指针之间的距离为环的长度。也就是说环中有n个节点,则第一个节点先在链表中移动n步,然后两个指针以相同的速度向前移动,当第二个指针指向环的入口节点时,第一个指针也已经走完一圈这个环,并且重新指向入口节点了。那么我怎么知道链表当中的环中有几个节点呢?
获取链表里的环中的节点数量是在第一步实现的。当第一步中快、慢指针相遇之后,从这两个指针指向的节点出发,并开始计数,当再次回到这个节点时,就得到了环中的节点数了。

3. 代码实现

3.1 我的思路

# 节点类
class ListNode(object):
	def __init__(self, value, next_node=None):
		self.value = value
		self.next = next_node

# 从链表中找出环的入口节点
def entry_node_of_loop(head):
	finded_nodes = {} # 已经出现过的节点
	cur = head
	while cur:
		if finded_nodes.get(cur.value) != 1:
			finded_nodes[cur.value] = 1
		else:
			return cur
		cur = cur.next
	# 若遍历完链表之后仍没有发现环的入口节点,表明此链表没有环
	return 

3.2 书中思路

# 节点类
class ListNode(object):
	def __init__(self, value, next_node=None):
		self.value = value
		self.next = next_node

# 判断链表中是否有环
def meeting_node(head):
	if head == None: # 空链表
		return
	slow = head.next
	if slow == None:
		return
	fast = slow.next
	while fast != None and slow != None:
		if fast == slow:
			return fast  # 这里返回的是环中的一个节点,这个节点不一定是入口节点(notice)
		slow = slow.next
		fast = fast.next
		if fast != None:
			fast = fast.next
	return
	
# 判断链表中是否有环		
def meeting_node(head):
	if head == None: # 空链表
		return
	slow = head
	fast = slow.next
	#while fast != None and slow != None: # 为甚还要判断slow是否为None,前面fast都判断过了啊
	while fast != None:
		if fast == slow: # 当快指针与慢指针再次相遇这种情况存在,则表明此链表是有环的
			return fast # 这里返回的是环中的一个节点,这个节点不一定是入口节点(notice)
		slow = slow.next # 慢指针先往前移动一步
		fast = fast.next # 快指针也往前移动一步
		if fast != None:
			fast = fast.next # 快指针再往前移动一步
	return
		
# 从链表中找出环的入口节点
def entry_node_of_loop(head):
	m_node = meeting_node(head)
	if m_node: # 表明链表中有环
		# 获取环的长度
		length = 1
		count = 0 # 走了几步
		p_node = m_node
		while p_node.next != m_node:
			length += 1
			p_node = p_node.next
		front = head # 领先length步的指针
		behind = head # 落后length步的指针
		while count < length:
			front = front.next
			count += 1
		while front == behind:
			front = front.next
			behind = behind.next
		return front
	return 

# 从链表中找出环的入口节点
def entry_node_of_loop(head):
	m_node = meeting_node(head)
	if m_node: # 表明链表中有环
		# 获取环的长度
		length = 1
		count = 1 # 走了几步
		p_node = m_node
		while p_node.next != m_node:
			length += 1
			m_node = m_node.next
		front = head.next # 领先length步的指针
		behind = head # 落后length步的指针
		while front == behind:
			front = front.next
			count += 1
			if count > length:
				behind = behind.next
		return front
	return 

4.总结

链表一般用到双指针有两种情况,一种是两个指针速度一样,但是始终间隔一定距离,另一种就是两个指针速度不一样,一快一慢。
如果条件允许可以使用额外的空间来记录链表,例如字典,因为字典中根据key来找value的时间复杂度是O(1),所以我一般使用字典来降低时间复杂度。其实在leetcode中的许多题,要降低时间复杂度都可以通过字典来实现,因为字典的相关操作时间复杂度低,当然字典所耗费的空间会更大一些。

5.参考文献

[1]剑指offer丛书

你可能感兴趣的:(算法)