287. **Find the Duplicate Number

287. **Find the Duplicate Number

https://leetcode.com/problems/find-the-duplicate-number/description/

题目描述

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.

Example 1:

Input: [1,3,4,2,2]
Output: 2

Example 2:

Input: [3,1,3,4,2]
Output: 3

Note:

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

解题思路

表面上这道题标定的是 medium, 实际上要想出来太难…

先给出关于这道题比较清晰的参考:

287. Find the Duplicate Number leetcode 的官方解答

leetcode笔记:Find the Duplicate Number

Find the Duplicate Number 找到重复数字

Find the Duplicate Number 牛人博客, LeetCode 有六百多题的解答.

题目的大意是,给定一个包含 n + 1个整数的数组,其中每一个整数的大小均在[1, n]之间,证明其中至少有一个重复元素存在。同时假设数组中只有一个数字出现重复,找出这个重复的数字。

注意事项:

不可以修改数组(假设数组是只读的) (否则直接用 442. **Find All Duplicates in an Array 的方法可解决)
只能使用常数空间
运行时间复杂度应该小于O(n^2)
数组中只存在一个重复数,但是该数可能重复多次

思路: 首先, 使用鸽笼(抽屉)原理可以证明, 这 n + 1 个数字中必然有重复数字. 可以将 n + 1 个位置想象成 n + 1 双袜子, 1 ~ nn 个数相当于抽屉, 要让 n + 1 双袜子放到 n 个抽屉中, 必然有一个抽屉要放置两双袜子, 翻译过来就是数组中至少有两个位置要取相同的数.

解决本题需要的主要技巧就是要注意到:由于数组的 n + 1 个元素范围从 1n,我们可以将数组考虑成一个从集合 {1, 2, ..., n} 到其本身(即集合本身)的函数 f。这个函数的定义为 f(i) = A[i]。基于这个设定,重复元素对应于一对下标 i != j 满足 f(i) = f(j)。我们的任务就变成了寻找一对 (i, j)。一旦我们找到这个值对,只需通过 f(i) = A[i] 即可获得重复元素。这变成了计算机科学界一个广为人知的“环检测”问题。

由于数组元素范围1n,不存在值为 0 的元素, 因此环的形状必然是 P 型的:

                x_0 -> x_1 -> ... x_k -> x_{k+1} ... -> x_{k+j}
                                    ^                       |
                                    |                       |
                                    +-----------------------+

由Robert Floyd提出的一个著名算法,给定一个ρ型序列,在线性时间,只使用常数空间寻找环的起点。这个算法经常被称为“龟兔”算法.
当我看完 142. Linked List Cycle II 的解法后(这道检测链表中环的问题的解答可以参考 Gitbooks : Linked List Cycle), 再来解决这道题就轻松很多.

为了用检测链表环的思路来解决这道题, 我们可以发现, 比如对于序列 {3, 1, 2, 3, 4}, 可以得到如下链表:

## 因为 nums[0] = 3, nums[nums[0]] = 3
0  ->  3  ->  
	   |___|	   

也就是说, 存在 (i, j)(0, 3), 使得 f[0] == f[3] == 3. 所以环是索引 3, 而值是 f[3] = 3. (举出这个例子是想说明, 当存在环时, 后面的 2, 4 之类的值也许不会访问到).

那检测链表中的环, 思路是设置快慢指针 (fastslow), fast 每次移动两步, 而 slow 每次移动一步, 当 fastslow 重合时(除了起始的时候), 说明链表中存在环, 而要找到环的位置, 详情见:

Gitbooks : Linked List Cycle 摘录下来:

譬如下面这个,环的起点就是 n2

        n6-----------n5
        |            |
  n1--- n2---n3--- n4|

我们仍然可以使用两个指针fastslowfast走两步,slow走一步,判断是否有环,当有环重合之后,譬如上面在n5重合了,那么如何得到n2呢?

首先我们知道,fast 每次比 slow 多走一步,所以重合的时候,fast 移动的距离是 slow 的两倍,我们假设 n1n2 距离为 an2n5 距离为 bn5n2 距离为 cfast 走动距离为 a + b + c + b,而 slowa + b ,有方程 a + b + c + b = 2 x (a + b),可以知道 a = c,所以我们只需要在重合之后,一个指针从 n1,而另一个指针从 n5,都每次走一步,那么就可以在 n2 重合了。

C++ 实现 1

综上, 代码为: 注意初始的情况

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
      	// 初始的时候, slow 和 fast 都应该是 nums[0] 的, 但由于这里使用的是 
      	// while 循环而不是 do...while, 所以 slow 和 fast 写成了下面的形式,
      	// do...while 的写法参见 leetcode 的官方解答.
      	// 下面先将 fast 移动两格, 而 slow 只移动一格
        int slow = nums[nums[0]], fast = nums[nums[nums[0]]];
        while (fast != slow) {
            fast = nums[nums[fast]];
            slow = nums[slow];
        }
		
      	// 当 fast 和 slow 相遇后, 要开始查找环的入口. 参见上面思路中 n1, n2, 的
      	// 图示, 这里将 fast 指向 n1, 而 slow 目前指向的是 n5, 然后每次让它们移动
      	// 一格, 最终相遇在 n2.
        fast = nums[0];
        while (fast != slow) {
            fast = nums[fast];
            slow = nums[slow];
        }
        return fast;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值