【二分查找】leetcode 287. 寻找重复数

287. 寻找重复数

题目描述

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数

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

示例1:

输入: nums = [1,3,4,2,2]
输出: 2

示例2:

输入: nums = [3,1,3,4,2]
输出: 3

提示

  • 1 < = n < = 1 0 5 1 <= n <= 10^5 1<=n<=105
  • n u m s . l e n g t h = = n + 1 nums.length == n + 1 nums.length==n+1
  • 1 < = n u m s [ i ] < = n 1 <= nums[i] <= n 1<=nums[i]<=n
  • n u m s 中只有一个整数出现两次或多次,其余整数均只出现一次 nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次 nums中只有一个整数出现两次或多次,其余整数均只出现一次

方法一:二分查找

解题思路

我们用 c n t [ i ] cnt[i] cnt[i] 表示 n u m s nums nums 数组中元素小于等于 i 的个数。假设重复的数是 target,那么 [ 1 , t a r g e t − 1 ] [1,target-1] [1,target1] 范围内的所有数都满足 c n t [ i ] < = i cnt[i]<=i cnt[i]<=i,而 [ t a r g e t , n ] [target,n] [target,n] 范围内的所有数都满足 c n t [ i ] > i cnt[i]>i cnt[i]>i。因此,具有单调性,可以利用二分查找来解题。

n u m s = [ 1 , 3 , 4 , 2 , 2 ] nums = [1,3,4,2,2] nums=[1,3,4,2,2] 为例,我们可以列出每个数字的 c n t cnt cnt 值:

nums1234
cnt1345

由此可以验证我们上面的推断,当 t a r g e t = 2 target = 2 target=2 时, [ 1 , 1 ] [1,1] [1,1] 范围内的所有数满足 c n t [ i ] < = i cnt[i]<=i cnt[i]<=i,而 [ 2 , 4 ] [2,4] [2,4] 范围内的所有数满足 c n t [ i ] > i cnt[i]>i cnt[i]>i

现在可以得出结论:

  • 1 < = i < = t a r g e t − 1 1 <= i <= target - 1 1<=i<=target1时, c n t [ i ] < = i cnt[i] <= i cnt[i]<=i
  • t a r g e t < = i < = n target<=i<=n target<=i<=n时, c n t [ i ] > i cnt[i]>i cnt[i]>i

代码

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int n = nums.size();
        int l = 1, r = n - 1, mid;
        while(l < r)
        {
            mid = (l + r) >> 1;
            int cnt = 0;
            for(int i = 0; i < n; i++)
                if(nums[i] <= mid)
                    cnt++;
            if(cnt > mid)
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }
};

复杂度分析

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)。二分查找的时间复杂度为 O ( l o g n ) O(logn) O(logn),每一轮二分查找都需要遍历数组的时间复杂度为 O ( n ) O(n) O(n),因此整体时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度: O ( 1 ) O(1) O(1)

方法二:快慢指针

解题思路

假设有这样一个样例子:[1,2,3,4,5,6,7,8,9,5]。如果我们按照上面的循环下去就会得到这样一个路径: 1 2 3 4 5 [6 7 8 9] [6 7 8 9] [6 7 8 9] . . .这样就有了一个环,也就是6 7 8 9。指针会一直在环中循环的前进。
这时我们设置两个一快(fast)一慢(slow)两个指针,一个每次走两步,一个每次走一步,这样让他们一直走下去,直到他们在重复的序列中相遇。
在这里插入图片描述
如上图,slow 和 fast 会在环中相遇,先假设一些量:起点到环的入口长度为m,环的周长为 c,在 fast 和 slow 相遇时 slow 走了 n 步。则 fast 走了 2n 步,fast 比 slow 多走了 n 步,而这n步全用在了在环里循环( n % c = = 0 n\%c==0 n%c==0)。
当 fast 和 slow 相遇之后,我们设置第三个指针 finder,它从起点开始和 slow (在 fast 和 slow 相遇处)同步前进,当 finder 和 slow 相遇时,就是在环的入口处相遇,也就是重复的那个数字相遇。

为什么 finder 和 slow 相遇在入口
fast 和 slow 相遇时,slow 在环中行进的距离是 n - m,其中 n % c = = 0 n\%c == 0 n%c==0。这时我们再让 slow 前进 m 步——也就是在环中走了 n 步了。而 n % c = = 0 n\%c == 0 n%c==0 即 slow 在环里面走的距离是环的周长的整数倍,就回到了环的入口了,而入口就是重复的数字。

我们不知道起点到入口的长度m,所以弄个 finder 和 slow 一起走,他们必定会在入口处相遇。

代码

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow = 0, fast = 0;
        slow = nums[slow];
        fast = nums[nums[fast]];
        while(slow != fast)
        {
            slow = nums[slow];
            fast = nums[nums[fast]];
        }
        int finder = 0;
        while(finder != slow)
        {
            finder = nums[finder];
            slow = nums[slow];
        }
        return finder;
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)。「Floyd 判圈算法」时间复杂度为线性的时间复杂度。
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值