LeetCode-287 寻找重复数 二分法
287. 寻找重复数
给定一个包含 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 = [1,1]
输出:1
示例 4:输入:nums = [1,1,2]
输出:1提示:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次进阶:
如何证明 nums 中至少存在一个重复的数字?
你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?
对于寻找重复数这种题,常规做法的话如哈希都可以做,但是本题的要求是不修改数组 nums
且只用常量级 O(1)
的额外空间。注意到本题给出了一个常规寻找重复数题目没有的条件:其数字都在 1 到 n 之间(包括 1 和 n)
二分法
记要找的重复数为 target,cnt(i)cnt(i)cnt(i) 表示给定数组 numsnumsnums 中不大于 iii 的数字个数。比如 cnt(3)cnt(3)cnt(3) 就是 numsnumsnums 中1, 2, 3的个数之和,显然 cnt(i)cnt(i)cnt(i) 是递增的。以数组 {1, 2, 3, 3, 4 ,5}
为例:
iii | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
cnt(i)cnt(i)cnt(i) | 1 | 2 | 4 | 5 | 6 |
根据上面提到的额外的条件,我们可以发现这样一个规律:对于小于 target 的数 i,cnt(i)=icnt(i)=icnt(i)=i,对于大于等于 target 的数 i,cnt(i)>icnt(i)>icnt(i)>i 。因此,我们要找的重复数 target,就是最小的使得 cnt(i)>icnt(i)>icnt(i)>i 的数字 i。可以看到,上例中 target 为3,当 i<3i<3i<3 时,cnt(i)=icnt(i)=icnt(i)=i,当 i≥3i\ge3i≥3 时,cnt(i)>icnt(i)>icnt(i)>i。
而我们回顾以下常规的二分法,是这样的:对于一个递增(有序)的序列 numsnumsnums 和 给定值 target,我们要找到最小的使得 nums[i]>targetnums[i]>targetnums[i]>target 的数字 i。
这就是这个题为什么可以使用二分法,递增序列当然是使用二分法的前提,我们用 cnt(i)cnt(i)cnt(i) 来合理地构造;然后根据具体的要找的条件来更新结果就好了。
常规二分法代码(返回索引):
int bin_search(vector<int>& nums, int target) {int n = nums.size();int l = 0, r = n-1;int mid;while (l<=r) {mid = (l+r) >> 1;if (nums[mid] < target) l = mid + 1;else if (nums[mid] > target)r = mid - 1;elsebreak;}return mid;
}
本题代码(C++):
class Solution {
public:int findDuplicate(vector<int>& nums) {int n = nums.size();int l = 0, r = n-1, ans = -1;while (l<=r) {int mid = (l+r) >> 1;int cnt = 0;for (int i=0; i<n; ++i) { // 计算cnt(mid),这里的mid就是我们上面公式中的icnt += nums[i] <= mid ? 1 : 0;}if (cnt<=mid) { // 比较cnt(mid)和midl = mid + 1;}else { // 若cnt(mid)>mid,则mid有可能是target:所有满足cnt(mid)>mid的值中最小的mid值即为target(即代码中的ans)ans = mid;r = mid - 1;}}return ans;}
};
双指针
我们将数组转换为链表,即将下标 nnn 和数 nums[n]nums[n]nums[n] 建立一个映射关系 f(n),将 nnn 作为下一个元素 f(n)f(n)f(n) 的志向。数组元素的取值范围在 [1,n][1,n][1,n] 之间,而数组长度为 n+1n+1n+1 ,所以按照数组元素取值一定不可能越界。然后利用链表判环的快慢指针方法找到相同元素。
详见:https://leetcode.cn/problems/find-the-duplicate-number/solution/287xun-zhao-zhong-fu-shu-by-kirsche/
class Solution {
public:int findDuplicate(vector<int>& nums) {int fast = 0, slow = 0;while (true) {fast = nums[nums[fast]];slow = nums[slow];if (slow == fast) break;}fast = 0;while (true) {slow = nums[slow];fast = nums[fast];if (slow == fast) return fast;}}
};