[LeetCode] 287. Find the Duplicate Number
Given an array of integers nums
containing n + 1
integers where each integer is in the range [1, n]
inclusive.
There is only one repeated number in nums
, return this repeated number.
You must solve the problem without modifying the array nums
and uses only constant extra space.
Example 1:
Input: nums = [1,3,4,2,2] Output: 2
Example 2:
Input: nums = [3,1,3,4,2] Output: 3
Constraints:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
- All the integers in
nums
appear only once except for precisely one integer which appears two or more times.
Follow up:
- How can we prove that at least one duplicate number must exist in
nums
? - Can you solve the problem in linear runtime complexity?
寻找重复数。
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/find-the-duplicate-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题意是在一个数组中找到重复的数字。数组包含 N + 1 个数字,数字在 1 <= x <= n 之间。两种解法。
1. 二分法。注意数组是无序的。当得到数组长度 len 和数组长度的一半 mid 之后,用 count 记录有多少个数小于等于中间数 mid。举个例子,如果数组长度是 10,mid 则是 5,这里 mid 指的是数组长度的一半。如果小于 mid 的个数大于数组的一半,说明重复的数字一定是小于 mid 的;反之如果大于 mid 的个数过半,重复的数字一定是大于 mid 的。用二分法逐渐逼近这个值,注意 return 的是 left。
时间O(nlogn) - 二分 logn * 每次找到 mid 之后要再把所有元素看一遍
空间O(1)
Java实现
1 class Solution { 2 public int findDuplicate(int[] nums) { 3 int len = nums.length; 4 // 因为数字的范围就是从1开始的,所以这里不是0,是1 5 int left = 1; 6 int right = len - 1; 7 while (left <= right) { 8 // 在 Java 里可以这么用,当 left + right 溢出的时候,无符号右移保证结果依然正确 9 int mid = left + (right - left) / 2; 10 int count = 0; 11 for (int num : nums) { 12 if (num <= mid) { 13 count++; 14 } 15 } 16 // 根据抽屉原理,小于等于 4 的个数如果严格大于 4 个 17 // 此时重复元素一定出现在 [1, 4] 区间里 18 if (count > mid) { 19 // 重复元素位于区间 [left, mid] 20 right = mid - 1; 21 } else { 22 // if 分析正确了以后,else 搜索的区间就是 if 的反面 23 // [mid + 1, right] 24 left = mid + 1; 25 } 26 } 27 return left; 28 } 29 }
JavaScript实现
1 /** 2 * @param {number[]} nums 3 * @return {number} 4 */ 5 var findDuplicate = function(nums) { 6 let start = 1; 7 let end = nums.length - 1; 8 while (start < end) { 9 let middle = Math.floor((start + end) / 2); 10 let count = 0; 11 // 计算总数组中有多少个数小于等于中间数 12 for (let i = 0; i < nums.length; i++) { 13 if (nums[i] <= middle) { 14 count++; 15 } 16 } 17 18 if (count <= middle) { 19 start = middle + 1; 20 } else { 21 end = middle; 22 } 23 } 24 return start; 25 };
2. 快慢指针。这个思路很难想到,参考142题。因为数组一定是有重复数字出现而且数字的范围不大于数组的长度,所以可以用快慢指针的思路做。当快慢指针相遇之后,再走第二遍,再次相遇的地方就是环的起点,也就是重复的数字。
时间O(n)
空间O(1)
Java实现
1 class Solution { 2 public int findDuplicate(int[] nums) { 3 int len = nums.length; 4 if (len > 1) { 5 int slow = nums[0]; 6 int fast = nums[nums[0]]; 7 while (slow != fast) { 8 slow = nums[slow]; 9 fast = nums[nums[fast]]; 10 } 11 slow = 0; 12 while (slow != fast) { 13 slow = nums[slow]; 14 fast = nums[fast]; 15 } 16 return slow; 17 } 18 return -1; 19 } 20 }
JavaScript实现
1 /** 2 * @param {number[]} nums 3 * @return {number} 4 */ 5 var findDuplicate = function(nums) { 6 const len = nums.length; 7 if (len > 0) { 8 let slow = nums[0]; 9 let fast = nums[nums[0]]; 10 while (slow !== fast) { 11 slow = nums[slow]; 12 fast = nums[nums[fast]]; 13 } 14 slow = 0; 15 while (slow !== fast) { 16 slow = nums[slow]; 17 fast = nums[fast]; 18 } 19 return slow; 20 } 21 return -1; 22 };