287. Find the Duplicate Number

287. Find the Duplicate Number

题目

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.

Note:
1. You must not modify the array (assume the array is read only).
2. You must use only constant, O(1) extra space.
3. Your runtime complexity should be less than O(n2).
4. There is only one duplicate number in the array, but it could be repeated more than once.

思路

在做这题之前,先做了一系列的无额外空间的数组题,例如:41. First Missing Positive

这些数组题的核心是交换数组中的数字,使得数字放在对应的位置,比如数字2在交换之后,会被放在第二位,也就是下标为1的地方。遍历整个数组,假设当前遍历下标为1,那么使得:nums[i] == nums[nums[i]-1],就可以保证数字在对应位上,或者遇到了重复的数字。之后就相当于利用题目条件,将无序的数组变得相对有序,然后就容易解了。

于是看到287这题的时候,第一反应就是类似的思想,交换数字!但发现题目要求不能修改数组,那么就想到了双指针。最初的想法(错误的)是固定住一个指针i,另一个指针按照数字nums[i]跳到nums[nums[i]-1]的地方,直到两个指针的数相等,就得到结果;或者指针不再变化时,就移动固定的指针。这种解法能解决部分情况,但遇到一个死循环但都不等于固定指针的时候,就GG了。

于是想到,跳来跳去的指针最终都会陷入死循环,那么死循环中必定有一个数是重复的。先证明一定存在死循环:
题目说有1~n共n个不同的数,但数组长度是n+1,于是一定有一个数重复。我们按照nums[nums[i]]来跳指针(为什么不用nums[nums[i]-1]?),那么一定会出现死循环,因为没有一次跳指针能跳出数组范围。

证明一定存在死循环之后,题目就变成了类似在单向链表中寻找回环入口,只需要证明这里的死循环入口下标就是重复的那个数。证明如下:
根据题意,只有一个数是重复的(数量可能为多个),而循环的入口就是第一个访问两次的数,按照我们的跳指针方法nums[nums[i]],只有可能是当i!=j && nums[i]==nums[j]时,有nums[nums[i]] == nums[nums[j]]。那么这个nums[i]就是我们要找的重复的数了。进一步可以得知,当发现nums[i] == nums[j]时,就可以判断这个值是重复的数,不需要再跳一步找下标是否相等了。

于是我们可以用双指针找回环的算法来解决这题,至于为什么这个算法是正确的,这里有个大大写了一篇很好的博文:http://blog.csdn.net/l294265421/article/details/50478818,而且287这题也有一个很好的解读:http://keithschwarz.com/interesting/code/?dir=find-duplicate

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        if(nums.size() < 2) {
            return -1;
        }

        int slow = nums[nums[0]];
        int fast = nums[slow];

        // cout << slow << ", " << fast << endl;

        while(slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
            fast = nums[fast];
            // cout << slow << ", " << fast << endl;
        }
        fast = nums[0];
        while(slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值