LeetCode287:Find the Duplicate Number

146 阅读2分钟

此题目规定一个n+1大小的数组,数组中元素的取值范围在1~n之间,仅有一个值重复k(k>=2)次,求:如何在不改变数组的情况下,使用 O(1)的空间 和 时间复杂度优于O(n^2) 的算法找出这个重复的值?

从题目的建议条件看来,对数组排序后再查重(破坏了数组原有的顺序),使用set表(O(n)的额外空间)都不被建议。查找其他大神的解析,发现以下两个方法可以满足要求:

  1. 二分法

    这道题也出现在Leetcode->explore->binary search类型下,说明leetcode默认此题有一种二分法的解题思路。但是数组中的元素并不是有序。所以不能直接用二分法。

    但是,如果从中位数的角度进行考虑。对于range(1,n)的无重复数组来说,小于等于m(1<=m<=n)的元素个数一定小于等于m。所以套用二分法思路,如果数组中 小于等于mid的元素数量 > mid,则重复元素一定在[1,mid]的区间内,否则在[mid+1,n]之间。代码如下:

int findDuplicate(vector<int>& nums) {
    int left = 0, right = nums.size() - 1, mid;
    while(left < right)
    {
        mid = left + (right-left)/2;
        int cnt = 0;
        for(auto i:nums)
        {
            if( i <= mid ) ++cnt;
        }
        
        if(cnt > mid) right = mid;
        else left = mid + 1;
    }
    return left;
}
  1. 链表的环的起始节点检测

    检测链表环的这个思路尤其巧妙。首先对数组进行了抽象, index认为是链表地址,val认为是链表的next。

    根据题目,可以有如下推论:

    2.1. index从0开始,而val从1开始,所以nums[0]永远不可能成为环。

    2.2. 如果一个环内所有的元素只出现一次。那么此环没有外部入口。从nums[0]出发,一定不会到达该环。

    2.3.重复的元素节点一定在一个环内。因为除去2.2.中形成封闭环的元素,剩下的元素本质上还是n+1个位置放n个元素,一定会产生环。

    2.4.重复的元素节点一定是该环的首节点。因为重复的元素节点至少有两个入口,一个提供给外部到达(从nums[0]出发到达),一个入口作为环的一部分。

    所以综上,原问题变成了寻找环的起始节点问题。代码如下:

int findDuplicate(vector<int>& nums) {
    int fast = nums[0], slow = nums[0];
    do{
        slow = nums[slow];
        fast = nums[nums[fast]];
    }while(fast != slow);
    
    slow = nums[0];
    while(fast != slow)
    {
        slow = nums[slow];
        fast = nums[fast];
    }
    
    return fast;
}