leetcode-[数组、双指针、二分搜索]-寻找重复数(287)

1、问题描述

给定一个包含n+1个整数的数组nums,其中,每个整数的取值范围为[1,n],根据鸽巢原理可知,至少存在一个重复的整数。假设只有一个重复的数,找出这个数。

2、解题思路

  • 边界条件与特殊情况:(1)nums中整数的个数小于2;
  • 问题分析:以nums=[1,3,4,2,3]为例,索引index的范围是在[0,n],元素值value的范围是[1,n],想象一下,把数组看成一个链表,数组中每一个位置上的元素值作为下一个元素的索引,当有元素重复时,会得到下面这样的带有环的索引链表:
    在这里插入图片描述
index value
0 1
1 3
2 4
3 2
4 3
  • 思路1:根据上面的分析,类比判断链表中是否存在环问题的解题思路,采用快慢指针来解决这个问题,整个思路分为两个步骤:
  • (1)检测环。具体地,快、慢指针fast、slow都从0开始,进行如下循环:slow每次进行一跳,而fast进行两跳,比如,一开始slow=i、fast=i,下一步中,slow=nums[i],而fast=nums[nums[i]]。当slow等于fast,循环停止,此时slow和fast都停在环上某个节点。
  • (2)找到环的入口。具体地,finder指针从0开始,slow从上一步中slow和fast在环中的相遇的开始,进行如下循环:slow和finder指针每次都进行一跳。当finder等于fast时,循环停止,此时finder指向的为环的入口。
  • 这里可能有点难理解的是:为什么finder和slow会在环的入口处相遇?我们可以证明一下:
  • 假设起点到环的入口处长度为m,环的周长为c,在检测环的过程中,快慢指针在环中相遇,此时,fast走了2n步,slow走了n步,根据以上假设可得出以下结论:
  • 由于两个指针在环中相遇,所以多走的n步一定是c的整数倍,即n%c=0;
  • fast和slow相遇时,slow在环中行走的距离是n-m,此时,若finder从起点开始,slow从相遇点开始,两者同步前进,每次一步,当finder走了m步到达环的入口处时,slow走了n-m+m=n步,而n又是c的整数倍,故两者会在环的入口相遇。
  • 先声明一下数据结构:
  • slow慢指针、fast快指针,finder指针。
  • 初始化,一开始slow和fast都为0,finder也为0
  • 处理逻辑: 检测环和找到环的入口。
  • 这个方法的时间复杂度为O(n)O(n),空间复杂度为O(1)O(1)
  • 思路2:一说到查找,且时间复杂度小于O(n2)O(n^2),则可以尝试二分查找,二分查找的思路如下:
  • 由于数组的元素的取值范围在[1,n],所以需要查找的数的区间也left=1、right=n,取该区间的中位数mid,统计数组中小于等于mid的元素个数count,如果count<=mid,则说明重复的整数在[mid+1,right]区间,否则在[left,mid]之间,如果left <right,则继续在子区间查找。
  • 由于进行了lognlogn次查找,统计小于mid的元素个数count时间复杂度为O(n)O(n),所以时间复杂度为O(nlogn)O(nlogn),空间复杂度为O(1)O(1)

3、代码实现

快慢指针实现:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
         if (nums.size() < 2){
            return 0;
        }
        
        int slow=0;
        int fast = 0;
        int finder = 0;
        while(true){
            slow = nums[slow];
            fast = nums[nums[fast]];
            if(slow == fast){
                break;
            }

        }

        while(true){
            slow = nums[slow];
            finder = nums[finder];
            if(slow == finder){
                break;
            }
        }
        return finder;
    }
};

二分搜索实现:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
         if (nums.size() < 2){
            return 0;
        }
        int left = 1;
        int right = nums.size() - 1;
        while(left < right){
            int mid = (left + right) /2;
            int count = countNums(nums,mid);
            if(count <= mid){
                left = mid + 1;
            }
            else{
                right = mid;
            }

        }
        return left;
        
    }
    int countNums(vector<int>& nums,int e){
        int count = 0;
        for(int i=0;i<nums.size();i++){
            if(nums[i] <= e){
                count ++;
            }
        }
        return count;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章