• 【二分查找】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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    复杂度分析

    • 时间复杂度: 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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    复杂度分析

    • 时间复杂度: O ( n ) O(n) O(n)。「Floyd 判圈算法」时间复杂度为线性的时间复杂度。
    • 空间复杂度: O ( 1 ) O(1) O(1)
  • 相关阅读:
    java计算机毕业设计西宁市农副产品物流信息系统源代码+数据库+系统+lw文档
    Shell 函数
    【测试开发之路】Java & Selenium自动化
    机械臂速成小指南(十四):多项式插值轨迹规划
    将PCAP转换为Json文件的神器:joy(安装篇)
    北斗导航 | RTD、RTK完好性之B值、VPL与HPL计算(附B值计算matlab源代码)
    vue3弹性布局 类似九宫格排列(多选选项)
    IMS各网元的主要功能
    【数据结构与算法 | 堆篇】力扣215
    vue+java 实现动态多列表头
  • 原文地址:https://blog.csdn.net/lele_ne/article/details/126283276