(no.23) Find the Duplicate Number 多种方法探讨

  • 题目描述
    这里写图片描述

  • 分析实现 法一
    不考虑空间复杂度必须为O(1)的条件,在满足1、3、4的条件下,对于找寻重复的数字来说,很简便的一种方法就是映射。将元素值作为新开数组的下标,进行出现次数的统计。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int len=nums.size()+1;
        vector<int> log(len,0);
        int result=0;

        for(int i=0;i<len;i++){
            log[nums[i]]++;
        }

        for(int i=0;i<len;i++){
            if(log[i]>1){
                result=i;
                break;
            }
        }


        return result;
    }
};
  • 分析实现 法二
    现在,我们需要考虑空间复杂度必须为O(1)的条件,也即需要写出一个满足上述四个条件的代码,怎么入手呢?

    回顾法一,其成功在于以重复数字作为下标的元素被累加了两次及其以上,我们找到元素值大于1的,也就找到了重复数字。可是现在,不能创建个以N为大小的数组呀,我们怎样才能抓住下标元素被累加到了两次及其以上呢?

    因为有重复的数字,那么对于value初始化为0,value = nums[value]来说,value会最终陷在一个循环里。如nums={1,3,4,2,2},那么value=1,3,2,4,2,4,2,4,…..。而这个循环里,就有我们的重复数字。

    那现在问题就变为如何发现这个循环,并且如何从循环里拿到重复数字。所以便有了下面的代码:

class Solution {
public:
    int findDuplicate(vector<int>& nums){
        if (nums.size() > 1){
            //开始起跑,slow一步一步跑,fast两步并一步跑
            int slow = nums[0];
            int fast = nums[nums[0]];
            //确定它俩陷在了循环里
            while (slow != fast){
                slow = nums[slow];
                fast = nums[nums[fast]];
            }

            //fast重新开始一步一步地起跑,当与陷入循环里的slow相遇时,即为所求
            fast = 0;
            while (fast != slow){
                fast = nums[fast];
                slow = nums[slow];
            }
            return slow;
        }
        return -1;
    }   
};
  • 法2 细节说明
    这里我们引入另一个加速的循环,value = nums[nums[value]],如此,通过两个循环的碰撞便能发现了循环并拿到重复的元素。

    那为什么这两个循环一定会相撞呢?我们用物理学里的速度路程(s=vt)来论证。

    我们把慢的循环叫slow,快的循环叫fast。因为是循环永无止境,所以追踪的路程s可以无限大。

    现在,想让fast追上slow,fast的速度为vf,slow的速度为vs(vs结合本题的意思就是value = nums[value]计算一次),并且vf=2*vs。我们可以列出公式,vs*t=vf*t+dis。其中dis为一开始slow和fast相隔的距离。

    从而,我们可以解出t=dis/vs。
    现来看dis的取值,slow和fast最多相距n的距离。

    综上,它们一定会相撞。因为从物理学中知道,路程无限的条件下,快速度的一定会追上慢速度的。并且只要在O(n)的时间复杂度下,我们就能让两个循环相撞了。

  • 分析实现 法3
    之所以会想到法2,是受法1映射的思想影响。现在,我们从中跳出来,看看还有没有其他的办法。

    分治法。

    我们要充分利用题目给的条件,“Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive)”

    n+1个数,每个数的值还只能是从1到n中取,那么我随便说一个1到n中的数,如果数组中小于等于它的数的个数竟然大于该数值本身,那么肯定是有重复的。

    举个例子,nums={4,3,6,5,4,1,2},我随便说个数:5,那么nums中小于等于5的元素个数为6,说明Duplicate Number的值比5小,就是因为Duplicate Number,才会造成数量反超的局面。

    代码实现如下,使用二分搜索连缩小范围,

    class Solution {
    public:
    int findDuplicate(vector<int>& nums){   
        int len=nums.size();
        int low=1;
        int high=len-1;
        int mid,count;
        while(low<high){
            mid=low+(high-low)/2;
            count=0;
            for(int i=0;i<len;i++){
                if(nums[i]<=mid)   //统计小于mid的元素个数 
                    count++;
            }
            if(count<=mid)
                low=mid+1;
            else         //说明有元素重复了不止一次 
                high=mid;   
        }
        return low;
    }
    };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值