142. 环形链表 II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
思考:
这道题其实本质是一道数学题,其中的数学思维尤为重要。
我们所要判断的东西其实有两个:一个是该链表是否是一个环形链表,一个则是找到入环的节点。
而这类问题,我们一般都可以通过双指针法进行求解,这里我们就会使用到快慢双指针。
若是环形链表,那么我们的快慢双指针就一定有机会在环内相遇,为了让两个指针必定相遇,我们可以让快指针一次走两格,慢指针一次走一格。这样若存在环,两者就必定相遇。
而接下来就是寻找环的入口的问题了,这里其实就是一个数学问题。
在相遇时,slow指针走过的结点为x+y,fast走过的结点数为x+y+n(y+z),而fast指针的速度是slow指针的两倍。所以x+y+n(y+z)=2(x+y)
进而可以得出x+y=n(y+z)
x=n(y+z)-y
由于n为任意正数,所以令n=1,x=z
而当n大于1时,表明快指针多跑了n-1圈后才相遇,而x=z并不受影响。
我们就可以定义两个指针index1,index2,index1从相遇的结点处出发,index2从头结点处出发,两者相遇时,恰好都走过x以及z的距离,在环形入口结点相遇。
双指针法:
有了上面的叙述,我们以及得出了其中的数学关系,所以我们的代码主要分两步走:1.判断是否有环 2.从相遇点开始重新定义两个指针,寻找入口。
#include<iostream>
using namespace std;
//Definition for singly-linked list.
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode* detectCycle(ListNode* head) {
ListNode* fast = head; //定义快指针
ListNode* slow = head; //定义慢指针
while (fast != NULL && fast->next != NULL)
{
slow = slow->next;//每次走一格
fast = fast->next->next;//每次走两格
if (slow == fast) //此时定义新的双指针,用于找到入口
{
ListNode* index1 = fast; //从交点处出发
ListNode* index2 = head; //从头部出发
while (index1 != index2) //当两者没相遇时
{
index1 = index1->next;
index2 = index2->next;
}
return index2;//两者相遇,找到入口
}
}
return NULL;//不存在环
}
};
参考:代码随想录
往期回顾:
面试题 02.07. 链表相交
LeetCode19. 删除链表的倒数第 N 个结点
LeetCode24. 两两交换链表中的节点
LeetCode206. 反转链表
LeetCode707.设计链表
LeetCode203.移除链表元素
LeetCode54. 螺旋矩阵
LeetCode59.螺旋矩阵II
LeetCode977.有序数组的平方
LeetCode844.比较含退格的字符串
LeetCode283.移动零
LeetCode27.移除元素
LeetCode26.删除有序数组中的重复项
LeetCode209.长度最小的子数组
LeetCode904. 水果成篮
LeetCode242.有效的字母异位词
LeetCode76.最小覆盖子串