287. Find the Duplicate Number 找到重复出现的数

题目:给定n+1个数,这个数的范围在1 ~ n间(包括1, n ) 且,有且仅有一个重复的数。找到这个重复的数。

(重复的数可以出现很多次,但只有一个重复的数)

一看这个题目:感觉方法是很多的,可以hash, 可以交换排序等。

但题目又给了很多约束条件:

1. 不能更改array

2. 只允许O(1)空间复杂度

3.时间复杂度不超过O(n^2)

有了这些约束,增加了这题的难度。

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.

在这里,介绍2种不同解法,第一种是基于binary search, 第二种是基于linklist 环的算法。

1.binary search解法

分析: 在1~n的范围内放了n+1个数,那么1~n范围是一个“拥挤”的状态。

假设mid = (1 + n ) / 2,有可能是 [1, mid] 拥挤,也有可能是(mid, n]拥挤(拥挤意味着有数重复出现)

那么什么时候[1, mid]拥挤呢? 当浏览array里面的数的时候  记录 <= mid的数的个数,记为count。

如果 count > mid, 那么是[1, mid] 部分拥挤。反而,是(mid, n]部分拥挤。

例如:我们的序列为4, 3, 7, 6, 2, 1, 2, 5 。 此时n = 7 , mid = 3,  count = 4 (3,2,1,2都<= 3)

所以,拥挤的部分是在[1, 3]范围内,在这个范围内会有重复的数!

之后就是进行二分,找到最终重复的数。

复杂度:

时间复杂度 O(n log n)

空间复杂度O(1) 

2.linklist 环的解法

这个思路借鉴了如何判断linklist有环,以及如何找到环的入口点。

例如序列:4, 3, 7, 6, 2, 1, 2, 5,我们把它想成linklist的形式

index 0 1 2 3 4 5 6 7

value 4 3 7 6 2 1 2 5


为了找到环:我们让一个慢指针一次走一步,让一个快指针一次走两步。

因为有环的存在,最终2个指针会在环内相遇。
如下图A为起点, B为2者相遇点, C为环的入口点,即我们想要找的那个点。
假设x为没有环前面的路程(黑色路程),a为环入口到相遇点的路程(蓝色路程,假设顺时针走), c为环的长度(蓝色+橙色路程)



此时慢指针走的路程为Sslow = x + m * c + a
快指针走的路程为Sfast = x + n * c + a
2 Sslow = Sfast
2 * ( x + m*c + a ) = (x + n *c + a)
x = (n - 2 * m )*c  - a
x = (n - 2 *m -1 )*c + c - a
即环前面的路程 = 数个 环的长度(为可能为0) + c - a
什么是c - a?这是相遇点后,环后面部分的路程。(橙色路程)
所以,我们可以让一个指针从原点开始走,让一个指针从相遇点开始继续往后走,
2个指针速度一样,那么,当从原点的指针走到环入口点的时候(此时刚好走了x)
从相遇点开始走的那个指针也一定刚好到达环入口点。
所以2者会相遇,且恰好相遇在环的入口点。

复杂度:

时间复杂度 O(n)

空间复杂度O(1)

binarysearch的测试时间:


binarysearch的代码:

    private int findDuplicateByBinarySearch(int[] nums) {
        int left = 1, right = nums.length - 1, mid;// searching in value range from [left, right]
        while (left < right) {
            mid = left + (right - left) / 2;
            int count = 0;
            for (int num : nums) {// counting the number in [left, mid] part
                if (num <= mid) {
                    count++;
                }
            }
            if (count > mid) {//the value range in [left, mid] is to crowded.
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }


linklist circle测试时间:


linklist circle的代码:

    private int findDuplicateByLinkListCircle(int[] nums) {
        if (nums.length > 1) {
            int slow = nums[0];
            int fast = nums[nums[0]];
            while (slow != fast) { //to find the meeting point
                slow = nums[slow];
                fast = nums[nums[fast]];
            }
            fast = 0;
            while (slow != fast) {// to find the entrance point
                slow = nums[slow];
                fast = nums[fast];
            }
            return slow;
        }
        return -1;
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值