算法1
(模拟链表) $O(n)$
两点前置知识:
1. 如何判断链表是否存在环?
双指针,一快(每次跑两格)一慢(每次跑一格),从链表首部开始遍历,两个指针最终都会进入环内,由于快指针每次比慢指针多走一格,因此快指针一定能在环内追上慢指针。而如果链表没环,那么快慢指针不会相遇。
2. 对于有环的链表,如何找到环的起点?
基于第一点,快慢指针相遇时,我们可以证明相遇的点与环起点的距离,一定和链表首部与环起点的距离相等。见图:
我们可以将数组视为一个(或多个链表),每个元素都是一个节点,元素的下标代表节点地址,元素的值代表next指针,因此,重复的元素意味着两个节点的next指针一样,即指向同一个节点,因此存在环,且环的起点即重复的元素。
为了找到任意一个环的起点(重复元素),我们只需要拿到一个链表的首部,然后利用前置知识即可解决问题。显然,0一定是一个链表的首部,因为所有元素值的范围在1 - n-1之间,即没有节点指向0节点。
题解流程即为:从0开始,快慢指针分别以2、1的速度向前遍历,当它们相遇时,将快指针置为0,继续分别以1、1的速度向前遍历,当它们再次相遇时,此时它们的下标就是题解。
时间复杂度分析:慢指针每次走一格,刚好遍历到链表尾部(即环起点)处结束,因此复杂度为$O(n)$
空间复杂度分析:$O(1)$
C++ 代码
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int f = 0, s = 0;
while (f == 0 || f != s) {
f = nums[nums[f]];
s = nums[s];
}
f = 0;
while (f != s) {
f = nums[f];
s = nums[s];
}
return s;
}
};
我把详细证明写这里了
1 2 3 4 5 6 7 8 9 10 9
这样再相遇之前, 快指针走了不止2圈环
字很好看!
关注点一致!!hhhhh
你这证得不对呀,兄弟。
快指针在环内并不一定只跑两圈,实际上是能够跑任意k圈的。
k并不一定等于2,这导致你的最后的那几步证明啥也证明不了2333.
实际上得计算出环的长度,然后把快指针重新放在0位置,设置速度为k(之前多跑的圈数),根据这个算出进入环之前的链表长度,然后得出起始点位置。
你是我的神
不对吧,假如nums数组是{1,2,0,3,3},这样前面三个数直接形成闭环了,我觉得这个成立的条件是重复的数被包含在这个环里。
这里说了不含0,因为0只能指向0这个闭环,然后其他下标指向了0的话是说明不了成环的
如果有多个环呢
leetcode 上的数据 [2, 3, 1, 0, 2, 5, 3] TLE 了
AcWing上的数据里没有0,0只能指向0这个闭环
如果有0,把所有数据+1再从索引0开始就好了,返回的时候减1就行
它不需要把每个元素都遍历一遍吧?
666啊!
能联想到模拟链表 太强了!!!!
相遇的点与环起点的距离,和链表首部与环起点的距离同余于环的周长 $a\equiv b \mod c$,特殊地,当 $a<c$ 或 $b<c$ 时,$a=b$。
这个是真的66666
%%%%%
膜拜大佬👍
不懂的可以看看,这个有图解
https://blog.csdn.net/weixin_36428079/article/details/112710458
看不懂啊,到底什么是环啊,我感受不到环的存在啊
tql
牛哇,大佬