题目
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:
输入:nums = [3,1,3,4,2]
输出:3
示例 3 :
输入:nums = [3,3,3,3,3]
输出:3
思路解析
实际上最简单的做法就是一次遍历即可,用Set维护,看是否出现过,这样很容易写,但是要额外的空间花销,时间空间都是O(n)
还有更省空间的做法,就是我们把nums当作链表来维护
数组:nums = [1,3,4,2,2]
我们把:索引 0 -> nums[0] 索引 1 -> nums[1] ...
理解为:
0 → nums[0] 1 → nums[1]
即:
i -> nums[i]
那么这个数组就变成:
0 → 1 → 3 → 2 → 4 → 2 → 4 → 2 ...
你会发现:
数字 2 出现重复
图结构中就形成了一个环
那么就可用链表找循环入口思路解决了
第一步:找相遇点(判断是否有环)
慢指针一次走一步
快指针一次走两步
如果存在环:
fast == slow
必然会相遇
第二步:找环入口
数学证明结论:
相遇后,把 slow 放回起点
slow 和 fast 每次走一步
再次相遇的地方就是环入口
环入口 = 重复数字
为什么第二次相遇一定是入口?(核心数学推导)
设:
a = 起点到环入口距离 b = 环入口到相遇点距离 c = 环剩余部分当第一次相遇时:
slow 走了 a + b fast 走了 2(a + b)因为 fast 比 slow 多走一圈:
2(a + b) = a + b + k(b + c)化简得:
a = k(b + c) - b可得:
a = c + (k-1)(b+c)说明:
从起点走 a 步
等价于从相遇点走 c 步
而 c 正是:
相遇点到环入口的距离
因此:
slow 从头走 fast 从相遇点走他们一定在入口相遇
代码
java
class Solution {
public int findDuplicate(int[] nums) {
// 慢指针:每次走一步
int slow = 0;
// 快指针:每次走两步
int fast = 0;
// 第一阶段:找到相遇点
// do-while 是因为必须先走一步
// 不能直接比较 0 == 0
do {
// slow 走一步
slow = nums[slow];
// fast 走两步
fast = nums[nums[fast]];
} while (fast != slow); // 相遇说明进入环
// 第二阶段:寻找环入口
// 将 slow 放回起点
slow = 0;
// slow 和 fast 同速前进
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
// 再次相遇的位置就是环入口
// 也就是重复数字
return slow;
}
}