leetcode 287 Find the Duplicate Number

Find the Duplicate Number
Total Accepted: 8673 Total Submissions: 25351 Difficulty: Hard

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:

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

这道题的解决方法很多最简单的是直接1到n每个数都去跑一次计个数,这样的话最坏情况是 O(n2) 的,另外可以用二分查找的方法,取 n2 , 然后看比 n2 大的有几个,看比 n2 小的有几个,重复的那个数如果在 [1,n2] 这个区间,那么这个区间的数一定多于 n2 ,同理如果重复的那个数在 [n2,n] 这个区间,那么这个区间的值一定多于 [1,n2] ,然后对找到的这个区间继续二分,重复上述过程,知道找出的区间只包含1个数,那就是重复的那个数。另外还可以用我之前在missing number一题中提到的做法来做。

但这道题还有一种很好的方法,采用快慢指针,这种方法是别人转述给我的,据说原帖是在这道题的Discuss区中。想法挺巧妙,我们把数组中的存的每个值作为我们下一个访问值的地址,例如一开始a[0] = 2, 那么下一步我访问的就不是a[1],而是a[2],如果a[2] = 5, 那么我第三步要访问的就是a[5]。这样的结果就是如果数组里面重复的数,那么访问的路线就会形成一个环,而这个环是走不出去的,所以快指针进入到环之后,就会在环里一直绕圈,知道慢指针也进入到环中,两者相遇。那是不是必然会走到环中呢?答案是肯定的,因为有一个值重复,所以其他的值总共只有n-1种选择,如果存在一种走法不会走到环中,那么沿着这种走法走到底n步时,由于之前的n-1步的取值都不同,因为只能有一个值重复,那么第n步只能走到环中。我们可以来看一个例子,输入数据为4 4 2 1 3:
这里写图片描述
其实指针走的路线就如图所示,可以发现4 3 1形成了一个环,我们设定快指针每次走两步,慢指针每次走一步,既然有环,那么虽然快指针走得快,但是最后会陷入到环中,一直绕,而当慢指针走入到环中,两者必然相遇,因为每次快指针比慢指针多走1步,当慢指针刚进入到环时,如果快指针距慢指针t步,那么走t次之后,它们就会会合。

了解到原理之后,我们就来看一下,具体算法应该怎样做,我们让快慢指针同时从0出发,然后记录它们走的步数,直到它们在环中相遇,假设此时慢指针走了k步,此时我们保持快指针不动,然后让慢指针继续往前走,重新从0开始记录一个步数p,当慢指针再和快指针会合时,走过的步数p就是环的周长。但我们其实想知道的是环的入口,因为环的入口地址,就是重复的那个元素,如图中的4. 这怎么实现呢,我想到一种简单有效的方法,不知原帖中的实现方式是否相同。我们把k减去p,这样得到一个步数k-p,而这个步数可以保证当慢指针走k-p步时,它还没有进入环,这不难理解因为当慢指针被快指针追上时,它必然还没走完一圈,因为我们知道当慢指针刚进入环时,快指针距慢指针最多p-1步,所以慢指针进入环后,最多走p-1步两者就会会合,所以慢指针必然没走完一圈,所以k-p步时,慢指针必然在环外。我们重新让慢指针回到初始位置,然后走k-p步,此时它距会合点p步,我们把快指针的速度也设为每次一步,它绕一圈再回到会合点,也需要p步,所以我们让快慢指针同时以相同速度往前走,当他们刚刚遇到一起时的那个地址,就是环的入口地址,也就是重复的那个值。这不难理解,假设会合点距入口m步,由于他们速度相同,距离会合点的距离也相同,所以必然同时达到会合点,而入口到会合点这一段路,它们不然也是“肩并肩”一起走的,所以它们必然也是同时到达入口的,而在到达路口之前,它们走的路线是不同的,一个在环外走,一个在环内走,所以当它们第一次碰面时,那必然就是在入口。

知道了算法,下面是具体实现的代码:

#include <iostream>
#include <stdlib.h>
using namespace std;

int findDuplicate(int* nums, int numsSize) 
{
    int quick = 0, slow = 0;
    int count_q = 0, count_s = 0, perimeter = 0, new_start;

    while(1)
    {
        quick = nums[quick];
        quick = nums[quick];
        count_q += 2;

        slow = nums[slow];
        count_s++;

        if(quick == slow)
        {
            do
            {
                slow = nums[slow];
                perimeter++;
            }while(slow != quick);

            new_start = count_s  - perimeter;
            slow = 0;
            for(int i = 0; i < new_start; i++)
            {
                slow = nums[slow];
            }

            do
            {
                slow = nums[slow];
                quick = nums[quick];
                new_start++;

            }while(slow != quick);


            return slow;
        }
    }
    return 0;
}

int main()
{
    int numsSize;
    cout<<"Input the input size:"<<endl;
    cin>>numsSize;

    int *nums = (int*)malloc(sizeof(int) * numsSize);

    for(int i = 0; i < numsSize; i++)
    {
        cin>>nums[i];
    }

    cout<<findDuplicate(nums, numsSize)<<endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值