Halo

A magic place for coding

0%

LeetCode解题报告(40)-- 287. Find the Duplicate Number

Problem

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:

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

Example 2:

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

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.

Analysis

  这道题考点是数组的操作,题目要求我们找到一个数组中重复的数字,这个重复的数字是唯一的,但重复次数不限。这类题目难点不在于求解,而在于算法的时间复杂度与空间复杂度,这里要求使用O(1)的空间,和低于O(n2)的时间复杂度。同时还有一条要求是不能修改原数组。我们针对这些要求逐个方法进行分析。
  很容易的一种做法是对数组进行快速排序(这样时间复杂度就满足),得出的结果从小到大排列。然后直接遍历数组,对比相邻两个元素,如果相邻元素是一样的,则找到重复元素。这种做法在时间复杂度上是合格的,也没有用到额外的存储空间,但是它修改了数组本身。
  然后我们来讨论一种优化的算法。我们可以在前面的一种算法上提高时间复杂度,思路还是一样的,我们只需要找到那个不在自己位置上的元素,不一定要通过排序才可以做到,只通过简单的交换也可以做到一样的效果。遍历数组nums,每个元素应该在的位置是num[i]应该在nums[nums[i] - 1]上,即nums[i] = nums[nums[i] - 1]。我们可以通过交换nums[i]nums[nums[i] - 1]来将元素放到它该在的位置。由于有1-n个位置,而只有n个数字,而且还有重复的数字,那么肯定会有缺失的数字的位置被重复的数字占据了,因此只需要检查nums[i] == i+1,若不满足,则找到了这个占据了其他元素的数字,这个就是重复的数字了。这样时间复杂度就降到了O(n)。
  可是这样还不是最满足题目要求的。我们如何要在不修改原有数组的情况下去实现呢?也就是说,我们只能遍历,然后就找到重复的数字了。这里要用到一个模型:将一个是数组抽象为一个环和一条线。详细原理分析以后再写(最近太忙了)。通过两个指针(1个快指针,1个慢指针,快指针一步跳一个,慢指针逐个遍历)进行遍历,当他们第一次相遇后,重置快指针为0,然后两个指针重新开始遍历(两个指针都是一步一个元素),最终相遇的点指向的就是重复的元素了。这种做法很巧妙,要充分了解了模型后才能运用,它没有修改原有数组的内容,整个过程也是对数组进行遍历,因此均满足题目的要求!

Solution

  根据分析的方法实现即可,本题在coding上没有什么难度。

Code

方法1:

1
2
3
4
5
6
7
8
9
10
11
int findDuplicate(vector<int>& nums) {
// Method 1
int size = nums.size();
sort(nums.begin(), nums.end());

for (int i = 0; i < size; i++) {
if (nums[i] == nums[i+1]) {
return nums[i];
}
}
}

方法2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int findDuplicate(vector<int>& nums) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == i+1 || nums[i] == nums[nums[i] - 1])
continue;
swap(nums[i], nums[nums[i] - 1]);
i--;
}
for (int i = 0; i < size; i++) {
if (nums[i] != i+1) {
return nums[i];
}
}
}

方法3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int findDuplicate(vector<int>& nums) {
// Method 3
int slow = nums[0], fast = nums[nums[0]];
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
fast = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return fast;
}

运行时间:约8ms,超过82.84%的CPP代码。

Summary

  这道题看上去是一道很简单的找重复元素的题目,题目难度是Medium,就算使用前两种做法也可以很快通过LeetCode的测试。但是如果自己对于算法要求比较高的话,不妨尝试一下,按照题目的要求进行设计。第三种方法非常巧妙,以后也可以使用到,我认为这题是非常好的题目,在这里和大家分享。如果有疑问或者建议,欢迎联系我,谢谢!

Welcome to my other publishing channels

Powered By Valine
v1.5.2