Leetcode 287. Find the Duplicate Number O(n)解法和O(nlogn)解法

来源:互联网 发布:windows.h是什么头文件 编辑:程序博客网 时间:2024/06/05 09:34

Find the Duplicate Number

题目连接:Find the Duplicate Number

Descript

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.

题意

在一个有n+1个由1-n的元素组成的数组中找出重复的那个元素是哪个,只有1个元素会重复,但是重复的次数可能不止1次。对时间复杂度和空间复杂度都有限制。

解题思路

这道题限制很多,感觉真的是对智商的考验。。。一开始我都是从求和以及平均数的角度去分析题目,发现由于可以多次重复导致多种情况的和可能会相等,不得不就此作罢。最后和同学讨论和上网查阅得到了两种解法,一个是二分法查找重复数,复杂度O(nlogn);另一种是映射找环法+快慢指针,复杂度O(n)。

二分法 O(nlogn)

若没有元素重复,则n/2左右两边的元素个数应该相等。基于这个思想,对于1-n,遍历一遍这个数组,记录小于等于n/2的元素个数,若大于n/2则说明重复的数小于等于n/2,否则大于n/2。接下来继续二分查找相应的区间即可找到。由于每次都需要遍历数组,复杂度为O(nlogn)。

class Solution {public:    int findDuplicate(vector<int>& nums) {        int n=nums.size()-1;        int low=1;        int high=n;        int mid;        while(low<high){            mid=(low+high)/2;            int count=0;            for(int num:nums){                if(num<=mid) count++;            }            if(count>mid) high=mid;            else low=mid+1;         }        return low;    }};

映射找环法+快慢指针 O(n)

这个算法真是神奇啊,我觉得想到的人都是牛逼的不行。。。我和同学几番讨论之后,把这个算法的理论正确性证明了一下,也在这里和大家分享一下,帮助大家理解这个算法。
记第i个元素为num[i],我们把k=num[k]记为一次操作,则这个算法的基本思想是通过一个每次进行一个操作的慢指针和一个每次进行两个操作的快指针进行迭代,来找到重复元素。首先由于下标是从0开始的,则下标是0到n的一个序列,映射到了包含重复数的n+1个数上。然后由于存在重复元素,所以一定存在两个以上的下标指向同一个数,这就会造成多次迭代后,快慢指针一定都会进入一个循环中。在环上以不同速度的两个指针一定会在某个位置上相遇。如下图所示:
示意图
其中A代表重复数(可以证明,两个指针一定是从重复元素进入环的),B代表两个指针相遇的点。一开始两个指针都先走了a步后进入环中,慢指针走了x(x < L,L为每圈长度,x可能是慢指针走了很多圈后不到一圈的距离,其实可以证明慢指针一定走不到一圈就和快指针相遇了,即下面的k1=0)步后和快指针相遇了。假设慢指针和快指针分别在环上走了k1和k2圈,则有:

2[a+(k1L+x)]a+2k1L+xa+xa+Lbaa=a+(k2L+x)=k2L=(k22k1)L=(k22k1)L=(k22k21)L+b=CL+b

这个结论有什么用呢?
天哪,太有用了好么!假如我们此时让一个新的慢指针从头开始走,同时旧的慢指针从相遇位置继续走,在旧指针走了C圈后,一定会和新指针在重复点上相遇,这就找到了重复元素是哪个了!实现代码如下。

class Solution {public:    int findDuplicate(vector<int>& nums) {        int fst=0,slow=0;        do{            fst=nums[nums[fst]];            slow=nums[slow];        }while(fst!=slow);        fst=0;        while(fst!=slow){            fst=nums[fst];            slow=nums[slow];        }        return fst;    }};

其实你自己在纸上亲自演示一遍这个算法的过程,你会更加理解的。

0 0
原创粉丝点击