• leetcode hot 100


    287. 寻找重复数

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

    示例 1:

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

    示例 2:

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

    说明:

    1 不能更改原数组(假设数组是只读的)。
    2 只能使用额外的 O(1) 的空间。
    3 时间复杂度小于 O(n2) 。
    4 数组中只有一个重复的数字,但它可能不止重复出现一次。

    思路一:二分法:

    参考:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/er-fen-fa-si-lu-ji-dai-ma-python-by-liweiwei1419/

    因为题目有几个要求,不能修改原数组,所以不能使用排序,然后统计相邻的元素是否相同;空间复杂度只能为O(1), 所以不能借助计数排序或者 HashMap统计元素个数。所以使用二分法来解决这个问题。

    让左右指针分别等于可能的最大值和可能的最小值,注意这个左右指针存储的不是下标,而是元素值;随后取左右元素的一半,遍历nums 数组,统计小于等于该中值的元素个数,如果元素个数大于mid, 说明前半段元素存在重复,说明[left, mid] 中存在重复,因为如果不存在重复的话,[left, mid]中元素最多为 (mid - left + 1), 肯定小于等于mid, 应该让rihgt = mid; 否则说明重复的元素在右半段,即重复元素存在于 [mid+1, right], 所以应该让left = mid + 1

     1 class Solution {
     2     public int findDuplicate(int[] nums) {
     3     
     4         // 二分查找小于等于中值的元素个数
     5 
     6         int left = 1, right = nums.length -1;
     7         while(left < right){
     8             int mid = (left + right) >>> 1; // 取左右的中值
     9             int cnt = 0;
    10             // 遍历数组,求出小于等于mid的元素的个数
    11             for(int num : nums){
    12                 if(num <= mid){
    13                     cnt++;
    14                 }
    15             }
    16 
    17             // 如果cnt严格大于mid, 说明重复元素一定在[left, mid]之间
    18             if(cnt > mid){
    19                 right = mid;
    20             }else{
    21                 left = mid + 1;  // 否则在[mid+1, right]之间
    22             }
    23 
    24         }
    25         return left;
    26     }
    27 }

    leetcode 执行用时:3 ms > 59.94%, 内存消耗:38.6 MB > 90.11%

    复杂度分析:

    时间复杂度:二分法每次把值的范围缩小一半,所以二分法的时间复杂度为O(logn), 另外每次二分的过程中需要遍历整个nums数组来统计小于等于 mid 元素的个数,这里的花费是O(n), 所以时间复杂度为O(nlogn)

    空间复杂度:只需要常量级别的变量空间,所以空间复杂度为O(1)

    思路二: 快慢指针

    参考:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/xun-zhao-zhong-fu-shu-by-leetcode-solution/

    我们对 nums[] 数组建图,每个位置 ii 连一条  i→nums[i]  的边。由于存在的重复的数字 target,因此 target 这个位置一定有起码两条指向它的边,因此整张图一定存在环,且我们要找到的 target 就是这个环的入口,那么整个问题就等价于 力扣142.环形链表II & 剑指offer 55. 链表中环的入口结点

    证明为什么快慢指针可以求得入环结点可以看力扣142.环形链表II & 剑指offer 55. 链表中环的入口结点

     1 class Solution {
     2     public int findDuplicate(int[] nums) {
     3         int slow = 0, fast = 0;
     4         do{
     5             slow = nums[slow];
     6             fast = nums[nums[fast]];
     7         }while(fast != slow);
     8 
     9         fast = 0;           // 快指针拉回到起点
    10         while(slow != fast){
    11             slow = nums[slow];
    12             fast = nums[fast];
    13         }
    14         return slow;
    15     }
    16 }
    leetcode 执行用时:0 ms > 100.00%, 内存消耗:38.4 MB > 99.09%,可以看到这个方式比思路一无论是空间还是时间都快很多

    复杂度分析:

    时间复杂度:O(n)。「Floyd 判圈算法」时间复杂度为线性的时间复杂度。简略证明也可以参考我文中贴的环形链表的文章
    空间复杂度:O(1), 只需要常数个空间存放若干变量
  • 相关阅读:
    [leetcode-79-Word Search]
    [leetcode-563-Binary Tree Tilt]
    [leetcode-561-Array Partition I]
    [leetcode-556-Next Greater Element III]
    [leetcode-554-Brick Wall]
    [leetcode-557-Reverse Words in a String III]
    [leetcode-532-K-diff Pairs in an Array]
    dev 官网
    DataTable 设置primarykey 后进行 Merge操作
    对称矩阵 一个简单的小把戏
  • 原文地址:https://www.cnblogs.com/hi3254014978/p/13780582.html
Copyright © 2020-2023  润新知