287. 寻找重复数
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 1
到 n
之间(包括 1
和 n
),可知至少存在一个重复的整数。
假设 nums
只有 一个重复的整数 ,找出 这个重复的数 。
示例 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
提示:
2 <= n <= 3 * 104
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
- 如何证明
nums
中至少存在一个重复的数字? - 你可以在不修改数组
nums
的情况下解决这个问题吗? - 你可以只用常量级 O ( 1 ) O(1) O(1) 的额外空间解决这个问题吗?
- 你可以设计一个时间复杂度小于 O ( n 2 ) O(n^2) O(n2) 的解决方案吗?
思路:
- 第一反应一般是对数组排序,然后遍历一次,遇到相邻的两个元素相等了,那这个数就是答案。但时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)(排序),会修改元素组,不满足进阶要求。
- 不难想到用一个哈希表
Set
存元素,即遍历一遍数组,元素都存入哈希表,当出现重复的时候就是答案。时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n),空间复杂度不满足条件进阶要求。 - 常见的一种方法就是遍历一遍数组,让数组中的元素与下标一一对应,如
nums[1] = 1,nums[2] = 2
,即让nums[i] = nums[nums[i]]
,0~n
共有n+1
个下标,但是数字是1~n
,故有一个下标对应的位置需要存两个数,这个数就是重复的,当然这也改变了原数组,不符合进阶要求。不改变原数组的话,也可以开辟一个新数组,用于计数,即遍历一次数组,temp[nums[i]]++
,之后遍历一次temp
数组,temp[i] > 1
的i
就是重复的数字,但不符合常量级 O ( 1 ) O(1) O(1) 的额外空间。 - 排除上述常见方法后,总算想到杀手锏,二分算法了。
注意:分的区间是数的范围,而不是索引的范围,如果在这个范围的数字个数大于区间长度,那么这个区间内一定有数字重复了。继续对该区间进行划分,直到区间长度为1为止。
时间复杂度:分治法为
O
(
l
o
g
n
)
O(logn)
O(logn),每次都要统计区间范围内的数字,复杂度为
O
(
n
)
O(n)
O(n),所以总的复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
class Solution {
public int findDuplicate(int[] nums) {
//数组中的数字在1~n之间,故按数字的范围二分,left和right起始值如下
int left = 1;//数字最小为1
//由题意知,nums.length = n + 1,故n=nums.length-1
int right = nums.length - 1;
while(left < right){//当left=right时,即只剩下一个数时停止循环,那个数就是结果
int mid = (left + right)/2;
//计算整个数组在前半部分的个数
int count = getCount(nums,left,mid);
if(count > (mid - left + 1)){//个数大于区间本该有的个数,该区间出现了重复数字
right = mid;
}else{
left = mid+1;
}
}
return left;//实际上最后返回left还是right都可以,因为他们最后相等
}
public int getCount(int[] nums,int left,int right){
int count = 0;
for(int num : nums){
//统计left~right(假设是1~4)这right-left+1(=4)个数字在整个数组中出现的次数
if(num >= left && num <= right){
count++;
}
}
return count;
}
}