描述
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
解析
若可以移动元素
如果数组可以移动元素,解法很多。比如,
先排序再比较;
hash;
抽屉原理
链表有环双指针
使用环形链表II的方法解题(142.环形链表II),使用142题的思想来解决此题的关键是要理解如何将输入的数组看作为链表。
首先明确前提,整数的数组 nums中的数字范围是[1,n]。考虑一下两种情况:
如果数组中没有重复的数,以数组[1,3,4,2]为例,我们将数组下标n和数nums[n]建立一个映射关系f(n),
其映射关系n->f(n)为:
0->1
1->3
2->4
3->2
我们从下标为0出发,根据f(n)计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推,直到下标超界。这样可以产生一个类似链表一样的序列。
0->1->3->2->4->null
如果数组中有重复的数,以数组[1,3,4,2,2]为例,我们将数组下标n和数nums[n]建立一个映射关系f(n),
其映射关系n->f(n)为:
0->1
1->3
2->4
3->2
4->2
同样的,我们从下标为0出发,根据f(n)计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推产生一个类似链表一样的序列。
0->1->3->2->4->2->4->2->……
综上
1.数组中有一个重复的整数 <==> 链表中存在环
2.找到数组中的重复整数 <==> 找到链表的环入口
至此,问题转换为142题。那么针对此题,快、慢指针该如何走呢。根据上述数组转链表的映射关系,可推出
142题中慢指针走一步slow = slow.next ==> 本题 slow = nums[slow]
142题中快指针走两步fast = fast.next.next ==> 本题 fast = nums[nums[fast]]
代码
类似抽屉原理
public int findDuplicate(int[] nums) { for (int i = 0; i < nums.length; i++) { if (nums[i] != i + 1) { if (nums[nums[i] - 1] == nums[i]) { return nums[i]; } swap(nums, i, nums[i] - 1); if (nums[i] != i + 1) { if (nums[nums[i] - 1] == nums[i]) { return nums[i]; } swap(nums, i, nums[i] - 1); } } } for (int i = 0; i < nums.length; i++) { if (nums[i] != i + 1) { return nums[i]; } } return -1; } public void swap(int[] nums, int a, int b) { if (a == b) { return; } nums[a] = nums[a] ^ nums[b]; nums[b] = nums[a] ^ nums[b]; nums[a] = nums[a] ^ nums[b]; }
链表有环双指针
public int findDuplicate(int[] nums) { int slow = 0; int fast = 0; slow = nums[slow]; fast = nums[nums[fast]]; while(slow != fast){ slow = nums[slow]; fast = nums[nums[fast]]; } int pre1 = 0; int pre2 = slow; while(pre1 != pre2){ pre1 = nums[pre1]; pre2 = nums[pre2]; } return pre1; }