【Leetcode】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.

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) extra space.
Your runtime complexity should be less than O(n2n^2).
There is only one duplicate number in the array, but it could be repeated more than once.

题目大意

给你一个数组,这个数组中只有一个重复的数字,让你找出数组中重复的数字。其中 1ainums.size()1\le a_i\le nums.size()
限制条件:

  1. 只能使用 O(1) 的空间,
  2. 不能改变原有数组
  3. 复杂度小于 O(n2n^2)

解题思路

由于题目中很多的限制,所以直接排序然后判断相邻元素是不是相等这个算法就不可以使用了,其实这里有一个复杂度为 O(nlog(n))O(nlog(n)) 的,就是借助二分答案然后判断,具体不展开说了,主要说一下快慢指针的使用。
因为给定的数组元素 1ainums.size()1\le a_i\le nums.size(),那么我们做一个关于下标和数组元素值映射,那么这个映射不是一对一的,是一个多对一的映射,比如说有一个数组 【1, 2, 4, 1, 3】,那么做一个映射【{0,3}->1,1->2,2->4,4->3】,然后我们去遍历,遍历的规则是 x=nums[x]x=nums[x],那么就有一个遍历序列为 {0,1,2,4,3,1}\{0,1,2,4,3,1\},显然这是一个环,那么我们要找的其实就是这个环的起点,那么我们设置两个指针,一个快指针fast,每次走两步;一个慢指针slow,每次走一步,那么肯定会在某个点相遇,这时我们将快指针fast 置为 00,然后每次走一步,直到两个指针相遇结束,这时相遇点就是环的起点,也是我们所求的重复的数,复杂度 O(n)O(n)

证明

证明1:为什么快慢指针在第一次一定会相遇。

1)  当快指针比慢指针慢 11 个单位的时候,那么这时候快指针走两步,慢指针走一步,正好相遇。
2)  当快指针比慢指针慢 22 个单位的时候,那么这时候快指针走两步,慢指针走一步,回到条件1)
n)  当快指针比慢指针慢 nn 个单位的时候,那么这时候快指针走两步,慢指针走一步,回到条件n-1)
那么根据数学归纳法,快慢指针一定会相遇。

证明2:为什么快慢指针在第一次相遇时,把快指针置为0之后,在第二次相遇时就是环的起点。

请看下图:
【Leetcode】287. Find the Duplicate Number(快慢指针+链表找环的起点)

因为有 a=ca=c,所以将快指针置为 00,每次走一步,那么下次快慢指针相遇的时候就是环的起点,也就是我们所求重复数的值。

代码如下

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