26 力扣热题刷题记录之第287题寻找重复数

系列文章目录

力扣热题刷题记录

文章目录

  • 系列文章目录
  • 前言
  • 一、背景
  • 二、我的思路
  • 三、官方的思路
    • 1.二进制位法
    • 2.快慢指针
    • 3.二分法
  • 总结

前言

每天进步一点点!!

一、背景

26 力扣热题刷题记录之第287题寻找重复数_第1张图片

来源:力扣
链接:https://leetcode-cn.com/problems/find-the-duplicate-number/

注意审题:
第一,不允许修改数组,所以不能用排序做;
第二,只能用常数个空间,故而不能用哈希,也不能开辟另一个数组存储数的个数;

由此,发现很容易想到的就是暴力求解,对于每一个数,都去跟后面的数进行比较,但这样容易超时。

二、我的思路

我没有思路啊啊,我的思路都被题目限制掉了,我去!!!

三、官方的思路

1.二进制位法

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        //采用二进制位的办法
        //如果对于重复的数字(把它展开为对应的二进制)
        //它相比统计(1-n)第i位的1个数会更大
        //如果重复数字的第i位是0,,不用管,不影响
        //如果重复数字的第i位是1,那么x>y
        //举例,1,2,3,和1,2,2,(1-3)的第二位为1加起来的和y=2
        //而1,2,2,有重复数字的第二位加起来的和x=3,由此x>y
        //实际上对于每一位都是如此,只要他当前位是1,我就可以累计求和
        //具体还原就是,满足x>y,说明当前位是重复数字的1位
        //因为当前位是0,不考虑是因为,一是它不满足x>y的条件,二是它是0位,加上去没意义

        int bitMax=31;//由于int最大数最高位是31位,而提示说了,最大数是n<=10000<2^31
        int len = nums.size();//len=n+1,题目有的

        //用最大数n(即len-1)去右移,直到移到最高位,满足 " !1=false ",跳出循环
        while (!(len-1)>>bitMax)
        {
            bitMax-=1;
        }

        
        int ans=0;  //用于记录最后的结果,即重复数
        //bit 循环用于移位,从而计算每一位,第一次移动0位,第二次1位……
        for (int bit=0;bit <=bitMax; bit++)
        {
            int x=0,y=0;//x用于统计实际某位为1的总数,y用于统计1-n的某位为1的总数
            for (int i=0;i<len;i++)
            {
                //如果nums[i]和第bit位的1进行与运算得到结果为1,统计X
                if (nums[i] & (1<<bit) )
                {
                    x+=1;
                }
                //如果i>=1(统计的是1-n之间),
                //并且 i 与第bit位的1进行与运算得到结果为1,统计Y
                if (i>=1 && (i & (1<<bit)) )
                {
                    y+=1;
                }               
            }
            //如果x>y,说明当前这个bit为是属于重复数当中的某一位
            //然后需要累加起来,用或运算,因为ans初始所有位为0
            if(x>y)
            {
                ans = ans | (1<<bit);
            }
        }
        return ans;
    }
};

2.快慢指针

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        //看了快慢指针的思路,现在来开始编码
        //首先如果有一个数字重复,按照把nums[i]和index都看成索引,就可构建环
        //有了环之后,其它没构建的就不用管它了,因为题目说了只有一个重复数
        //第一步,构建环,第二步找到环的入口(重复数)
        //此时把快指针重新放入起点(相遇时,快指针比慢指针多走了2n-n=n步)
        //设环的周长为C,则n%c==0(因为n步都用在了绕环上面),然后起点到环的入口m
        //慢指针的位置为环的入口开始走了n-m,再走m步就可以到达环入口,
        //所以最后相遇点就是重复数

        int fast=0,low=0;//定义快慢指针,其为数组的索引
        //相遇时停止
        while (nums[low]!=nums[fast] || fast==0 )
        {
            //慢指针一步,然后快指针两步
            low = nums[low];
            fast = nums[fast];
            fast = nums[fast];
        }
        //cout<<"相遇时fast   "<

        fast = 0;//相遇后,fast回到起点一步步走,最后跟low相遇
        while (nums[low]!=nums[fast] )
        {
            //慢指针一步,然后快指针两步
            low = nums[low];
            fast = nums[fast];
        }
        //cout<<"相遇时fast   "<
        return nums[fast];
    }
};

3.二分法

注意,原来的二分是针对数组有序,根据要搜的值和数组中间的值进行比较,依次对半对半排除。

而这里的用法,是它的稍微变化。也就是说,不一定是比较数组的值,而是可以比较另一个变量,只要这个变量满足一定的条件即可。

具体解释如下:

26 力扣热题刷题记录之第287题寻找重复数_第2张图片

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        //排个序,重复的数字一定聚集在一起
        //但是不修改数组,故而不能排序
        int n = nums.size();
        int l = 1, r = n - 1, ans = -1;
        while (l <= r) {
            int mid = (l + r) >> 1;//右移除以2,找寻中点
            int cnt = 0;
            for (int i = 0; i < n; ++i) {
                //假设mid就是要找的那个数,然后统计cnt,
                //如果mid真的是目标数,会符合规律cnt>mid,所以就能求解
                cnt += (nums[i] <= mid);
            }
            if (cnt <= mid) {
                l = mid + 1;
            } else {
                r = mid - 1;
                ans = mid;
            }
        }
        return ans;
    }
};

总结

这几个解法都比较难想,也不容易被理解,大家用心慢慢看就可以了,我也是看了好几天,呜呜呜!

你可能感兴趣的:(力扣刷题记录,leetcode,算法,散列表)