Skip to content
本页目录

0287-寻找重复数

https://leetcode.cn/problems/find-the-duplicate-number

给定一个包含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 <= 10^5
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次

进阶:

  • 如何证明 nums 中至少存在一个重复的数字?
  • 你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?

思路1 : 暴力法(超时)

暴力法,遍历两次,查询是否有重复的数字 因为复杂度为 O(n^2) 会超时

csharp
public class Solution {
    public int FindDuplicate(int[] nums) {
    	for(int i=0; i<nums.Length - 1; i++){
    		for(int j = i+1; j<nums.Length; j++){
    			if(nums[i] == nums[j]){
    				return nums[j];
    			}
    		}
    	}
    	return -1;
    }
}

思路2 : 哈希表

使用哈希表储存次数,这个方法同样没有使用到 [1..n] 这个特性

csharp
public class Solution{
	public int FindDuplicate(int[] nums){
		Dictionary<int,int> dict = new Dictionary<int,int>();
		for(int i=0; i<nums.Length; i++){
			if(!dict.ContainsKey(nums[i])){
				dict.Add(nums[i],1);
			}
			else{
				return nums[i];
			}
		}
		return -1;
	}
}

时间复杂度 O(n) 哈希表查找 O(1) 所以最终复杂度是 O(n) 空间复杂度 O(n)

进阶解法 : 二分查找

需要使用到 [1..n] 这个特性 我们先从 1..n,我们定义 count[i] 表示数组中小于等于i的数总和, 如果没有重复的数,那么有 count[i] = i 当出现重复的数字时: 我们取中间一个数 mid,当重复数字出现在 [1..mid] 中时,那么必然有 count[mid] > mid 反之重复数字出现在 (mid..n] 中 我们使用这个特性折半查找

csharp
class Solution{
	public int FindDuplicate(int[] nums){
		int left = 1;
		int right = nums.Length - 1;
		int ans = -1;
		while(left <= right){
			int mid = left + (right - left) / 2;
			int count = 0;
			for(int i = 0; i < nums.Length ; i++){
				if( nums[i] <= mid){
					count ++;
				}
			}
			if(count <= mid){
				left = mid + 1;
			}
			else {
				right = mid - 1;
				ans = mid;
			}
		}
		return ans;
	}
}

时间复杂度 O(nlogn)

进阶解法2 : 二进制位运算

我们把每个数的二进制位数都统计出来,如果有一个重复的数字,那么他所在的位的1的个数就会大于原来的个数 如示例: 1,3,4,2,2 他们的二进制排列如下

13422xy
第 0 位1100022
第 1 位0101132
第 2 位0010011

我们把 x > y 的位数出去来,就可以还原多出来的那个数字的二进制位,进而还原出原来的数字

有效性证明:如果只有2位重复数组,那么只会增加1的个数,其余不变

如果有3位或以上重复,那么会有缺失的数字

那么同样的位数多的1会增加,缺失的数组如果是0,那么不会受影响,如果是1,则他小于我们统计的常规个数,所以不影响最终判断。

csharp
class Solution{
	public int FindDuplicate(int[] nums){
        //定义32位数组,因为C#中int是32位的
        //每一位单独计算
        int ans = 0;
        int bit_max = 31;
        while (( nums.Length >> bit_max ) > 0) {
            bit_max -= 1;
        }
        for(int bit = 0; bit <= bit_max; bit++){
            int x = 0, y = 0;
            for(int i=0; i<nums.Length; i++){
                //这里是把1左移对应的位数
                if( (nums[i] & (1 << bit)) != 0){
                    //统计位数
                    x+=1; 
                }
                //然后从1统计到n
                if(i>0 && ( (i & (1 << bit)) != 0 )){
                    y+=1;
                }
            }
            if(x > y){
                ans = ans | 1 << bit;
            }
        }
        return ans;
    }
}

进阶解法3 :环

快慢指针法

csharp
class Solution{
	public int FindDuplicate(int[] nums){
        int slow = 0;
        int fast = 0;
        while(true){
        	slow = nums[slow];
        	fast = nums[nums[fast]];
            if(slow == fast){
                break;
            }
        }
        fast = 0;
        while(nums[slow] != nums[fast]){
        	slow = nums[slow];
        	fast = nums[fast];
        }
        return nums[slow];
    }
}

Released under the MIT License.