题目描述
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 1
到 n
之间(包括 1
和 n
),可知至少存在一个重复的整数。
假设 nums
只有 一个重复的整数,找出 这个重复的数。
样例
输入:nums = [1,3,4,2,2]
输出:2
输入:nums = [3,1,3,4,2]
输出:3
输入:nums = [1,1]
输出:1
输入:nums = [1,1,2]
输出:1
限制
2 <= n <= 3 * 10^4
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一个整数 出现 两次或多次,其余整数均只出现 一次。
算法
(双指针) $O(n)$
- 每个数字的下标都是
0
到n
范围内的,且每个数的值都是1
到n
。定义下标为i
的节点,其值为nums[i]
,next
指针也为nums[i]
。注意到,除了下标为 0 所代表的节点外,每个节点都至少有一条入边(至少有一个节点next
等于自己的下标)。除此之外,由于有一个重复的nums[i]
,所有 有且仅有一个非0
节点必定有两条或以上的入边。所以此题可以当做 Linked List Cycle II 来处理,即i
位置的值和next
都是nums[i]
。 - 定义
first
和second
快慢指针,初始时都指向0
节点开始找环。然后first
每次前进一个位置,second
每次前进两个位置。两个指针相遇后,其中一个指针重新指向0
节点,直到再次相遇。剩余部分请参考 Linked List Cycle II 中的算法证明。
时间复杂度
- 参见 Linked List Cycle II 时间复杂度部分,整个数组仅遍历常数次,故时间复杂度为 $O(n)$。
空间复杂度
- 仅使用常数的额外空间。
C++ 代码
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int first = 0, second = 0;
do {
first = nums[first];
second = nums[nums[second]];
} while (first != second);
first = 0;
while (first != second) {
first = nums[first];
second = nums[second];
}
return first;
}
};
原文中“注意到,除了下标为 0 所代表的结点外,每个结点都至少有一条入边(至少有一个结点 next 等于自己的下标)。”,这里是不是有些纰漏?假设输入为[2,2,2,2,2],下标为1,3,4的结点好像都没有入边。
现在假设数组中存在一个重复的数字
这个情况属于有一个重复数字,但是重复多次吧。题解里面
说明
的最后一条有提到。这个测试用例是LeetCode的,我写了一个错误的方法在这个用例上WA了。看错了,我找时间重新看下这道题
这个算法在这个场景下没问题,只需要保证有图中存在从
0
可达的一个环,就可以找到重复的数字。没必要每个点都存在入边。嗯嗯,是的,感谢大佬回复!
为什么有重复元素时数组可以当作环形链表来做呢
题解中更新了更详细的解释
能解释下为什么设置起始时指向0嘛,而不是nums[0]
这是实现的细节,因为用的是
do while
循环这思路好炫