【Leetcode】287. 寻找重复数

这篇博客探讨了在一个1到n的整数数组中,如何在不修改数组且仅使用常量级额外空间的情况下找出重复的数。分析了排序、哈希表和计算等传统方法的局限性,并详细介绍了利用二分法和快慢指针两种O(nlogn)时间复杂度的解决方案。二分法通过判断小于等于目标数的元素个数来找到重复数,而快慢指针则构建了一个环形链表的概念,找到环的入口即为重复数。

题目链接:287. Find the Duplicate Number

题目描述:

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

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 的额外空间。

分析:

寻找重复数的常用思路:

1)排序+遍历

排序可以使用归并排序、堆排序、快速排序等时间复杂度为O(n logn)的算法,然后再遍历找出重复数。需要修改数组nums。

2)哈希表

可以使用set 或map,也可以自己构建哈希表。如果不修改nums,那么构建哈希表的空间复杂度就是O(n),如果修改nums,那么构建哈希表的空间复杂度就是O(1)。时间复杂度都是O(n)。

3)计算

如果一共有n+1个元素,其取值都在1~n之间,并且其中只有一个元素出现了2次,其他元素都出现了且只出现了一次。那就可以用n+1个元素的和减去1~n这n个元素的累加和,结果就是那个重复出现的元素。时间O(n),空间O(1)。没有修改nums。

但是本题要求不修改nums,额外空间是O(1),而且虽然只有一个重复出现的元素,但是该元素出现了2次及以上。所以上述三种方法都不适用。

经过查看题解分析思路如下:

1)二分法

cnt[i]表示nums中数据小于等于i的个数。i∈[0,n]。

如果共有n个元素,取值范围为1~n,且不存在重复元素,即每个元素都出现且只出现一次,那么得到cnt[i]==i。

如果共有n+1个元素,取值范围为1~n,只有一个元素出现2次,其他元素都出现且只出现1次。如果出现2次的元素为target,那么对于i∈[1,target-1],cnt[i]==i,对于i∈[target,n],cnt[i]==i+1.

如果共有n+1个元素,取值范围是1~n,只有一个元素重复出现,且出现的次数多于2次,那么一定有一些元素没有出现过。假设某个没有出现过的元素为x。若x<target,那么对于i∈[1,x-1],cnt[i]==i,对于i∈[x,target-1],cnt[i]<i;若x>target,那么对于i∈[target,n],cnt[i]>i。

综上:对于i∈[1,target-1],cnt[i]<=i,对于i∈[target,n],cnt[i]>i。即cnt[i]是随着i递增的

所以目标就是在1~n之间找到一个数target,使nums中<=target的数的个数大于target。且这样的target尽可能小。

因为i和cnt[i]都是有序的,所以可以用二分法进行查找。其实也不必先把cnt[i]都求出来,然后二分去判断cnt[i]和i的大小。这样的时间复杂度是O(n^2),空间O(n)。

可以二分遍历i,对于i再确定对应的cnt,不满足条件再更换区间。这样需要的时间复杂度是O(n logn),空间是O(1)。

js代码如下:

var findDuplicate = function(nums){
    let length=nums.length;
    let l=1,r=length-1;
    let mid,cnt=0,res;
    while(l<=r){
        mid=l+((r-l)>>1);
        cnt=0;
        for(let i=0;i<length;i++){
            if(nums[i]<=mid){
                cnt++;
            }
        }
        if(cnt<=mid)
            l=mid+1;
        else{
            r=mid-1;
            res=mid;
        }
    }
    return res;
}

2)快慢指针

由于一共有n+1个元素,取值都在1~n之间,如果创建一个由i指向nums[i]的链表,一定可以成环形链表。那么重复出现的元素就是环形链表入环处的元素。

nums=[1,3,4,2,2]

链表为:1->3->2->4->2->4->2->....,可见,从2开始入环。

求环形链表入口元素的分析,可以参考:环形链表II

代码如下:时间O(n),空间O(1)。

var findDuplicate = function(nums) {
    let slow=0;
    let fast=0;
    while(true){
        slow=nums[slow];
        fast=nums[fast];
        fast=nums[fast];
        if(slow===fast)
            break;
    }
    fast=0;
    while(fast!==slow){
        fast=nums[fast];
        slow=nums[slow];
    }
    return slow;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值