LeetCode-287. 寻找重复数

题目来源

287. 寻找重复数

题目详情

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1n之间(包括 1n),可知至少存在一个重复的整数。

假设 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) 的解决方案吗?

题解分析

解法一:排序遍历

  1. 本题最简单的解法就是对数组进行从小到大排序,再遍历数组判断某个数字是否出现了多次。
  2. 在遍历数组的时候,除了使用比较法,也可以利用位异或的知识。因为如果两个相邻的元素相同,那前面元素的异或结果接连与这两个元素进行异或的结果其实与异或之前是相同的。
class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);// 对数组排序
        int prepre = 0, pre = 0;
        for(int i=0; i<n; i++){
           int temp = (pre ^ nums[i]);
           if(temp == prepre){
               return nums[i];
           }
           prepre = pre;
           pre = temp;
        }
        return nums[n-1];
    }
}

解法二:二分法

  1. 这里可以换一个角度思考问题。因为这个数组很特殊,它里面的元素的范围都在[1,n]之间,所以重复的元素必然也出现在[1,n]之间。
  2. 可以利用以上的性质,对于一个元素i,记数组中所有小于等于i的元素个数为cnt[i]。这个cnt[i]与重复的元素是存在一些关系的:假设重复的元素为target,在重复元素之前的i,它的cnt[i]必然小于等于i,而在重复元素之后的i,它的cnt[i]必然大于i,而且cnt数组也存在递增的关系。
  3. 比如对于一个输入数组:[1,3,4,2,2],它的cnt数组和i的关系如下:
    image

image

  1. 示例中重复的整数是 2,我们可以看到 [1,1] 中的数满足cnt[i]≤i,[2,4] 中的数满足cnt[i]>i 。
  2. 如果知道 cnt[] 数组随数字 i 逐渐增大具有单调性(即 target 前 cnt[i]≤i,target 后 cnt[i]>i),那么我们就可以直接利用二分查找来找到重复的数。
class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        int left = 1, right = n-1;
        int ans = 0;
        while(left <= right){// 小于等于并不是小于
            int mid = (left + right) >> 1;
            int cnt = 0;
            // 计算小于等于当前mid的元素个数
            for(int i=0; i<n; i++){
                if(nums[i] <= mid){
                    cnt++;
                }
            }
            // 以重复的数字所在位置为界线,进行二分
            if(cnt <= mid){
                left = mid + 1;
            }else{
                right =  mid - 1;
                ans = mid;// 这里暂时记录答案
            }
        }
        return ans;
    }
}

解法三:二进制

image

class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        int intmaxbit =  32;
        int bits = 32;
        // 求出n的最高位为1的位数
        while(((n-1) >> 32) == 0){
            bits -= 1;
        }
        int ans = 0;
        // 求数组各元素的第bit位为1的个数
        for(int bit=0; bit<bits; bit++){
            int x = 0, y = 0;
            for(int i=0; i<n; i++){
                x += ((nums[i] & (1 << bit)) != 0 ? 1 : 0);
                if(i > 0)
                    y += ((i & (1 << bit)) != 0 ? 1 : 0);
            }
            if(x > y){
                ans |= (1 <<  bit);
            }
        }
        return ans;
    }
}

解法四:快慢指针-找环的入口

这题有一个比较巧妙的解决方法就是使用快慢指针,因为这n+1个数中一定存在所有的1~n,并且其中还有一个重复的元素。利用这个性质,可以构建出一张图,即建立 i->nums[i]的图,因为存在一个重复的元素,这个重复的元素必然有多个点指向它。又因为除了0以为,其他的点一定有另一个点指向它,所以会产生闭环。

后续可以使用链表中找环的入口的方法寻找重复元素即环的入口。

class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        // 建立 i->nums[i]的图,因为存在一个重复的元素,这个重复的元素必然有多个点指向它。又因为除了0以为,其他的点一定有另一个点指向它,所以会产生闭环
        int slow = nums[0], fast = nums[nums[0]];
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[nums[fast]];
        }

        // 从0开始继续遍历找环的入口
        int first = 0, second = slow;
        while (first != second) {
            first = nums[first];
            second = nums[second];
        }
        return first;
    }
}
posted @ 2021-12-24 10:55  Garrett_Wale  阅读(129)  评论(0编辑  收藏  举报