寻找重复数(力扣第287题)
287.寻找重复数
给定一个包含n+1个整数的数组 nums,其数字都在1到n之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
分析:
该题要求相当苛刻,数组是只读的,不能使用额外空间,那么借助哈希表的方法也就不行了,时间复杂度则要求不能使用暴力法,数组中只有一个重复的数字,但是这个重复出现的数字可能不止重复一次。
一开始做起这个题也没有头绪,所以也参考了其他人的解法,主要使用的是二分法和快慢指针。
(1)二分法
这个数组乍一看,并不是一个有序数组,并不能直接使用二分法,但是经过一些处理,就能造出一个升序数组出来。首先明确一点,该数组一共有n+1个元素,这些元素值都在1-n之间,那么我们可以定义一个数组count[i],用于记录给定数组中小于等于i的元素个数,其中1=<i<=n。同时定义一个变量target,表示数组中重复的元素。
对于原数组中在[1,targrt-1]区间内的元素来说,其满足 count[i] <=i;
对于原数组中在[target,n]区间内的元素来说,其满足 count[i] > i;
解释一下为啥是count[i] <= i,如果原数组中重复的元素只出现两次,因为原数组的大小是n+1,那么1-n这个范围内的所有元素都会出现,所以此时count[i] ==i;如果原数组中重复出现的元素出现的次数大于两次,那么1-n这个范围内有的元素并不会出现,不会出现的元素可被认为由重复元素替代了,对于不会出现的元素分为两种情况:
(1)没出现的元素i位于[1,targrt-1]内,那么[i+1,targrt-1]区间内的元素k对应的count值应该都减去1,此时就是count[k] < k了;
(2)没出现的元素j位于[target,n]内,那么[target,j-1]区间内的元素k对应的count值都应该加上1,此时也满足count[k]>k了;
比如,上面的示例2,[3,1,3,4,2],其count与nums值的对应关系如下:
nums | 1 | 2 | 3 | 4 |
---|---|---|---|---|
count | 1 | 2 | 4 | 5 |
我们要找的就是上面这个表中,第一个count[i] > i的那个值,这个值就是重复值。
相当于原始数组的元素值作为count值的索引(1-n),然后对count数组进行二分查找,寻找第一个count值大于其索引值的的那个元素,这元素的count索引值就是最终的结果。
实现代码:
public int findDuplicate2(int[] nums) {
int n = nums.length;
int left = 1;
int right = nums.length-1;
while (left < right){
int mid = (right + left) / 2;
int count = 0;
for (int i = 0; i < n; i++) {
if (nums[i] <= mid){
count++;
}
}
if ( count <= mid ){
left = mid + 1;
}else {
right = mid;
}
}
return left;
}
参考文档:
https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode 题解 - 数组与矩阵.md#5-有序矩阵的-kth-element