【算法题】Acwing 13.14找出数组中重复数字

题库13.找出数组中重复的数字

题目内容如下:
在这里插入图片描述
分析:很容易想到,要找重复的数字,不就是要我去统计每个数字出现的次数咩!因为是整数数组,且数字范围在0~n-1,还可以直接将数字作为下标,cnt[nums[i]]就对应了nums[i]这个数字出现的次数,只需要遍历一遍数组即可,时间复杂度是O(n),但是需要一个额外的数组cnt[n],增加了空间复杂度O(n),代码就是这样的:

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int i,n;
        n=nums.size();
        
        vector<int> cnt(n,0);
        //判断是否存在不在0~n-1这个范围的数
        for(i=0;i<n;i++)
             if(nums[i]<0||nums[i]>=n)
                return -1;
       //遍历的同时去统计每个数字出现的次数
        for(i=0;i<n;i++){
           cnt[nums[i]]++;
           if(cnt[nums[i]]>1)
             return nums[i];
        }
          
       return -1;
    }
};

注意如果只用了一次循环,判断是否存在不在0~n-1范围的数,同时去统计各个数字出现的次数,如果有>=2的就返回——问题出现在,在出现不合法的数字前,就有可能找到重复出现2次的数字,那么会返回那个数字,而不是-1。因此——要先循环一遍判断是不是存在不合法的数字!
题解里面的
题解修改数组
数组大小为n,数字范围是0~n-1,实际上可能存在没有重复数字的情况,即每个数字出现一次。
题解思想是交换数组中的数字,使得nums[i]=i:

  • 如果nums[i] != i,nums[i] = x,此时nums[x] = x,那么x就出现了不止一次,直接返回x;
  • 如果nums[i] != i,nums[i] = x,且此时nums[x] != x,x应该在nums[x]这个位置上,故交换nums[i]和nums[x],交换以后不需要i++,直接重复上面的判断;
  • 如果nums[i] = i,直接i++;

代码是去遍历数组,如果存在第一种情况就直接返回,结束;如果是第二种情况,就继续去交换(此时nums[i]已经变化,不能i++,去判断下一个数),那么是否存在死循环??——考虑最坏的情况,就是n个数字,没有重复的,且i为0的时候,就去一直重复情况2,直到后面的n-1个数字都到了正确的位置,之后就一路i++,结束了!时间复杂度依旧是O(n),但是没有增加额外的空间,代码如下:

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int i,n=nums.size();
        int ans=-1;
        int temp;
        //判断是否存在不在0-n-1范围内的数
        for(i=0;i<n;i++)
         if(nums[i]<0||nums[i]>=n){
                return -1;
            }
            
        //将每个数放在正确的位置上
        for(i=0;i<n;){
           
            
            if(nums[i]!=i&&nums[nums[i]]==nums[i]){
                ans=nums[i];
                break;
            }
            else if(nums[nums[i]]!=nums[i]){
                temp=nums[nums[i]];
                nums[nums[i]]=nums[i];
                nums[i]=temp;
            }
            else i++;
        }
        return ans;
    }
};

题库14.不修改数组找出重复的数字

题目链接14. 不修改数组找出重复的数字
题目内容如下:
在这里插入图片描述
此题与之前不同的地方在于——数组长度是n+1,数字范围是1~n,根据抽屉原理(n个抽屉放n+1件物品),至少存在一个数字不止出现一次! 需要注意的是,题目中并未像13题一样说明,数组中可能出现不在1~n范围的数字,一次默认数组内数字都是该范围内的。
此题依然可以直接遍历数组,统计数组中各数字出现的次数,但是需要O(n)的空间复杂度。

【借鉴】很有意思的题解是——利用二分,数字范围是1~n,将其分成[1 , n/2 ]和[ n/2 + 1 ,n ]两个区间,分别统计这两个数字区间内数字的数量,肯定有一个区间内的数字个数比数字区间(end-start+1)更大;那么再将这个区间二分,直到最后区间只剩下一个数字,这个数字就是答案!代码如下:

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n =nums.size();
        //be和end表示数字区间
        int be=1,end=n-1;
        //在循环外就给mid赋值,是因为数组[1,1],不会进入循环
        int mid=(be+end)/2;
        //统计数字区间的数字个数
        int cnt;
        int i;
        while(be<end){
            cnt=0;
            mid=(be+end)/2;
            //统计
           for(i=0;i<n;i++)
             if(nums[i] >= be && nums[i] <= mid)
             	cnt++;
            //在前面区间  
           if(cnt > mid-be+1)
              end = mid;
            //在后面区间
           else
              be = mid + 1;
        }        
        //最后be=end
        return end;
    }
};

需要注意的是,最后的数字区间一定是只有一个数,这个数字就是答案,此时be = end,退出循环,故返回be和end都ok,但是不可以返回mid!!!!因为最后可能是be = mid + 1然后be = end,此时mid !=be !=end,返回min是错误的!

😥😥😥😥 Acwing更像是考技巧??大家的代码都好精简,好高级!学习!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值