目录
一、题目
已知一个单链表中存在环,求进入环中的第一个节点。
//struct ListNode
//{
// int m_nKey;
// ListNode * m_pNext;
// ListNode(int key) : m_nkey(key),m_pNext(nullptr) {}
//};
// 已知一个单链表中存在环,求进入环中的第一个节点
ListNode* GetFirstNodeInCircle(ListNode * pHead)
{
}
思路
首先判断是否存在环,若不存在则结束。本题已经说明存在环,故可以不进行这一步操作。我们采用快慢指针进行遍历(快指针每次后移2个节点,慢指针每次后移1个节点),两个指针必会在环内某个节点第一次相遇[1],且入环后的循环次数必然不会超过环内节点个数[2]。再将快指针移至头节点,改为每次后移1个节点,慢指针按原来方式后移,交点必为入环节点[3]。
证明
[1] 两个指针必会在环内某个节点第一次相遇
1. 首先快指针必然先入环,所以若存在相遇,必然与慢指针在环内相遇
2. 由于在环内相遇,慢指针必入环
3. 慢指针入环后可以转换为“小学二年级”的追及问题(起始路程为快指针到慢指针的节点数,由于 v(快指针) - v(慢指针) = 1(节点/次),所以必然可以追上 <=> 两个指针必会在环内某个节点第一次相遇)
[2] 入环后的循环次数必然不会超过环内节点个数
1. 根据 [1] 的结论,我们可以计算入环后的循环次数。我们选取最差情况(上图即为最差情况的示例),即快指针在慢指针的后一节点,此时路程差为(环内节点数 - 1) ,
路程差 / 速度差 = 时间(操作数)<= 环内节点数 - 1
故,入环后的循环次数必然不会超过环内节点个数
[3] 快指针移至头节点,改为每次后移1个节点,慢指针按原来方式后移,交点必为入环节点
令直链的节点数为m,则慢指针的移动次数也为m,环内节点数为n,入环后第 l 个节点相交 (l < n) [2]中已证。
分类讨论:m > n
慢指针和快指针第一次相遇时,慢指针移动操作数为 m + l
则有 (m + l) * 2 = m + l + kn ①
我们采用反证法,当操作完成后,慢指针操作数为 m + l + m
将①代入 有m + l + m = m + kn
刚好为入环点
m <= n
慢指针和快指针第一次相遇时,慢指针移动操作数为 m + l
则有 (m + l) * 2 = m + l + n ①
我们采用反证法,当操作完成后,慢指针操作数为 m + l + m
将①代入 有m + l + m = m + n
也刚好为入环点
故快指针移至头节点,改为每次后移1个节点,慢指针按原来方式后移,交点必为入环节点。
代码
//已知一个单链表中存在环,求进入环中的第一个节点
ListNode* GetFirstNodeInCircle(ListNode* pHead)
{
ListNode* fast = pHead, * slow = pHead;
while (fast != slow) // 寻找环内第一次相遇的节点
{
fast = fast->m_pNext->m_pNext;
slow = slow->m_pNext;
}
fast = pHead; // 快指针返回头部
while (fast != slow) // 相交时为入环节点
{
fast = fast->m_pNext;
slow = slow->m_pNext;
}
return slow;
}
二、总结
双指针 + 数学 + 环链表