给定一个包含 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
提示:
1 <= n <= 105nums.length == n + 11 <= nums[i] <= nnums中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
- 如何证明
nums中至少存在一个重复的数字? - 你可以设计一个线性级时间复杂度
O(n)的解决方案吗?
1. 核心矛盾:带着镣铐起舞
这道题最狠的地方在于它的限制条件:
- 不能修改数组(不能排序,不能原地打标记)。
- 只能用 额外空间(不能用哈希表/Set)。
- 时间复杂度要快(通常指 )。
2. 生活案例:迷宫里的“死循环”
想象你在玩一个**“盲盒迷宫”**游戏:
- 迷宫里有很多房间,每个房间都有一个编号。
- 每个房间中心都有一张纸条,上面写着下一个房间的编号。
- 规则是: 按照纸条上的数字走。
为什么会有重复数字?
如果两个不同的房间里,纸条上写了同一个编号(重复数字),这意味着有两条路通向同一个房间。一旦你进入这个结构,你就会陷入一个圆形死循环,永远走不出来。而那个循环的入口,就是我们要找的重复数字。
3. 代码实现与注释(JavaScript)
你可以直接把这段带详尽注释的代码贴进笔记里:
JavaScript
/**
* @param {number[]} nums
* @return {number}
*/
var findDuplicate = function(nums) {
// --- 第一阶段:寻找相遇点(确定迷宫里有环) ---
// 我们派两名选手:slow(乌龟,每次走一步)和 fast(兔子,每次走两步)
let slow = nums[0];
let fast = nums[nums[0]];
// 只要有重复数字,兔子最终一定会在环里追上乌龟(相遇)
while (slow !== fast) {
slow = nums[slow]; // 乌龟走一步
fast = nums[nums[fast]]; // 兔子跑两步
}
// --- 第二阶段:寻找环入口(抓到那个重复的数) ---
// 此时两人相遇了。根据数学推导:
// 从“迷宫起点”到“环入口”的距离,正好等于从“当前相遇点”绕环回到“环入口”的距离。
// 我们让🐢回到起点(坐标 0),并🐰降速变得和乌龟一样慢(每次走一步)
slow = 0;
while (slow !== fast) {
// 两人以同样的速度前进
slow = nums[slow];
fast = nums[fast];
}
// 当他们再次相遇时,所在的房间编号就是环的入口,也就是那个重复的数字!
return slow;
};
4. 深度解析:为什么这样能成功?
在掘金分享时,建议加上这几点深度思考,会显得内容很有厚度:
-
抽象转化: 题目把数字范围限制在 ,而数组长度是 。这意味着我们可以把
nums[i]看作是一个指向下标i的指针。 -
数学逻辑: 假设起点到环入口距离为 ,入口到相遇点距离为 ,环长度为 。
- 推导出 ,这意味着从起点出发走 步,和从相遇点出发走 步,最终都会停在环入口。
-
复杂度优势: 时间复杂度 ,空间复杂度 ,完美契合题目严苛的要求。