leetcode hot 100 - 287. 寻找重复数

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2

示例 2:

输入: [3,1,3,4,2]
输出: 3

说明:

1 不能更改原数组(假设数组是只读的)。
2 只能使用额外的 O(1) 的空间。
3 时间复杂度小于 O(n2) 。
4 数组中只有一个重复的数字,但它可能不止重复出现一次。

思路一:二分法:

参考:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/er-fen-fa-si-lu-ji-dai-ma-python-by-liweiwei1419/

因为题目有几个要求,不能修改原数组,所以不能使用排序,然后统计相邻的元素是否相同;空间复杂度只能为O(1), 所以不能借助计数排序或者 HashMap统计元素个数。所以使用二分法来解决这个问题。

让左右指针分别等于可能的最大值和可能的最小值,注意这个左右指针存储的不是下标,而是元素值;随后取左右元素的一半,遍历nums 数组,统计小于等于该中值的元素个数,如果元素个数大于mid, 说明前半段元素存在重复,说明[left, mid] 中存在重复,因为如果不存在重复的话,[left, mid]中元素最多为 (mid - left + 1), 肯定小于等于mid, 应该让rihgt = mid; 否则说明重复的元素在右半段,即重复元素存在于 [mid+1, right], 所以应该让left = mid + 1

 1 class Solution {
 2     public int findDuplicate(int[] nums) {
 3     
 4         // 二分查找小于等于中值的元素个数
 5 
 6         int left = 1, right = nums.length -1;
 7         while(left < right){
 8             int mid = (left + right) >>> 1; // 取左右的中值
 9             int cnt = 0;
10             // 遍历数组,求出小于等于mid的元素的个数
11             for(int num : nums){
12                 if(num <= mid){
13                     cnt++;
14                 }
15             }
16 
17             // 如果cnt严格大于mid, 说明重复元素一定在[left, mid]之间
18             if(cnt > mid){
19                 right = mid;
20             }else{
21                 left = mid + 1;  // 否则在[mid+1, right]之间
22             }
23 
24         }
25         return left;
26     }
27 }

leetcode 执行用时:3 ms > 59.94%, 内存消耗:38.6 MB > 90.11%

复杂度分析:

时间复杂度:二分法每次把值的范围缩小一半,所以二分法的时间复杂度为O(logn), 另外每次二分的过程中需要遍历整个nums数组来统计小于等于 mid 元素的个数,这里的花费是O(n), 所以时间复杂度为O(nlogn)

空间复杂度:只需要常量级别的变量空间,所以空间复杂度为O(1)

思路二: 快慢指针

参考:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/xun-zhao-zhong-fu-shu-by-leetcode-solution/

我们对 nums[] 数组建图,每个位置 ii 连一条  i→nums[i]  的边。由于存在的重复的数字 target,因此 target 这个位置一定有起码两条指向它的边,因此整张图一定存在环,且我们要找到的 target 就是这个环的入口,那么整个问题就等价于 力扣142.环形链表II & 剑指offer 55. 链表中环的入口结点

证明为什么快慢指针可以求得入环结点可以看力扣142.环形链表II & 剑指offer 55. 链表中环的入口结点

 1 class Solution {
 2     public int findDuplicate(int[] nums) {
 3         int slow = 0, fast = 0;
 4         do{
 5             slow = nums[slow];
 6             fast = nums[nums[fast]];
 7         }while(fast != slow);
 8 
 9         fast = 0;           // 快指针拉回到起点
10         while(slow != fast){
11             slow = nums[slow];
12             fast = nums[fast];
13         }
14         return slow;
15     }
16 }
leetcode 执行用时:0 ms > 100.00%, 内存消耗:38.4 MB > 99.09%,可以看到这个方式比思路一无论是空间还是时间都快很多

复杂度分析:

时间复杂度:O(n)。「Floyd 判圈算法」时间复杂度为线性的时间复杂度。简略证明也可以参考我文中贴的环形链表的文章 空间复杂度:O(1), 只需要常数个空间存放若干变量
上一篇:Word2vec之CBOW 模型


下一篇:热门博客阅读及缓存提速