持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情
题目(Find the Duplicate Number)
链接:https://leetcode-cn.com/problems/find-the-duplicate-number
解决数:1807
通过率:64.6%
标签:位运算 数组 双指针 二分查找
相关公司:amazon microsoft google
给定一个包含 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
提示:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
- 如何证明
nums
中至少存在一个重复的数字? - 你可以设计一个线性级时间复杂度
O(n)
的解决方案吗?
思路
- 数组只读——不能去移动数组,不能排序
- 常量空间——不能使用哈希表,不能新建数组再排序
- 时间复杂度小于——不能用暴力法
- 只有一个数字重复,但可能重复多次
二分查找除了对索引二分,还有值域二分
-
数组元素是 1 - n 中的某一个,出现的位置不确定,但值域是确定的。
- 对索引二分,一般用于有序数组中找元素,因为索引的大小可以反映值的大小,因此对索引二分即可。
- 对值域二分。重复数落在 [1, n] ,可以对 [1, n] 这个值域二分查找。
-
mid = (1 + n) / 2
,重复数要么落在[1, mid]
,要么落在[mid + 1, n]
。 -
遍历原数组,统计 <= mid 的元素个数,记为 k。
-
如果
k > mid
,说明有超过 mid 个数落在[1, mid]
,但该区间只有 mid 个“坑”,说明重复的数落在[1, mid]
。 -
相反,如果
k <= mid
,则说明重复数落在[mid + 1, n]
。 -
对重复数所在的区间继续二分,直到区间闭合,重复数就找到了。
时间复杂度:二分法,但二分法内部遍历了一次数组,综合为 空间复杂度:
const findDuplicate = (nums) => {
let lo = 1;
let hi = nums.length - 1; //题目注明了:nums.length == n + 1
while (lo < hi) {
const mid = (lo + hi) >>> 1; // 求中间索引
let count = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] <= mid) {
count++;
}
}
if (count > mid) {
hi = mid;
} else {
lo = mid + 1;
}
}
return lo;
};
快慢指针法
- 分析这个数组,索引从 ,值域是 。值域,在索引的范围内,值可以当索引使。
- 比如,nums 数组:
- 以 nums[0] 的值 4 作为索引,去到 nums[4]
- 以 nums[4] 的值 2 作为索引,去到 nums[2]
- 以 nums[2] 的值 1 作为索引,去到 nums[1]……
- 从一项指向另一项,将nums数组抽象为链表:4->2->1->3->2,如下图,链表有环。
有环链表,重复数就是入环口
- 题目说数组必存在重复数,所以 nums 数组肯定可以抽象为有环链表。
- 题目转为:求该有环链表的入环口。因为入环口的元素就是重复的链表节点值。
- 怎么求入环口?可参考我另一篇大白话题解 有环链表怎么求入环口。
快慢指针法 代码
const findDuplicate = (nums) => {
let slow = 0;
let fast = 0;
while (true) {
slow = nums[slow];
fast = nums[nums[fast]]; // slow跳一步,fast跳两步
if (slow == fast) { // 指针首次相遇
fast = 0; // 让快指针回到起点
while (true) { // 开启新的循环
if (slow == fast) {// 如果再次相遇,就肯定是在入口处
return slow; // 返回入口,即重复的数
}
slow = nums[slow]; // 两个指针每次都进1步
fast = nums[fast];
}
}
}
};