目录
https://leetcode.com/problems/find-the-duplicate-number/
给定一个元素值在[1, n]范围内、长度为n+1的数组(特性:至少有一个数字重复出现),假设只有一个重复数字,找到该数字。
一、问题描述
测试用例:
Example 1:
Input: [1,3,4,2,2]
Output: 2
Example 2:
Input: [3,1,3,4,2]
Output: 3
二、代码实现
1、桶排序
桶排序是将符合某种共同特性的元素,比如相同元素的数量放在同一个桶中(以元素值为下标的桶中)。这里采用类似桶排序的思想,遍历整个数组,将下标为当前元素值的元素做个已访问标记(将其乘以-1、将其乘以一个大于所有元素的因子factor、或者直接将其交换到下标为当前元素值的位置处),如果该元素已经访问过,则说明当前元素重复。
class Solution {
//对于当前元素nums[i],将元素nums[nums[i]/factor]或nums[nums[i]]加上已访问标志(让其成为factor的倍数,从而大于等于factor)
public int findDuplicate7(int[] nums) {
int factor = nums.length; //n+1
for (int i=0; i<nums.length; i++) {
int index = (nums[i] >= factor) ? nums[i] / factor : nums[i];
if (nums[index] % factor == 0) { //即nums[index]是factor的倍数,说明已经访问过
return (nums[i] >= factor) ? nums[i] / factor : nums[i];
}
nums[index] = nums[index] * factor;
}
return -1; //error-case
}
//对于当前元素nums[i],将元素nums[nums[i]%factor]加上已访问标志(将其加上factor的倍数,倍数大于等于1)
public int findDuplicate6(int[] nums) {
int factor = nums.length; //n+1
for (int i=0; i<nums.length; i++) {
//int index = (nums[i] - 1) % factor; //[3,1,3,4,2]
int index = nums[i] % factor;
if (nums[index] / factor != 0) { //如果倍数不等于0,即倍数大于等于1,说明已经被访问过了
return nums[i] % factor;
}
//nums[index] *= factor; //[3,1,3,4,2]
nums[index] = nums[index] + nums[index] * factor; //添加已经被访问的标志
}
return -1; //error-case
}
//对于当前元素nums[i],将元素nums[nums[i]]加上已访问标志(hashNum[nums[i]] == 1)
public int findDuplicate5(int[] nums) {
int[] hashNum = new int[nums.length];
for(int i = 0; i < nums.length; ++i) {
if(hashNum[nums[i]] == 0) {
hashNum[nums[i]]++;
} else {
return nums[i];
}
}
return -1; //error-case
}
/对于当前元素nums[i],将元素nums[Math.abs(nums[i])]加上已访问标志(将其乘以-1)
public int findDuplicate4(int[] nums) {
int flag = -1;
for (int i=0; i<nums.length; i++) {
int index = (nums[i] > 0) ? nums[i] : -nums[i];
if (nums[index] < 0) {
return (nums[i] > 0) ? nums[i] : -nums[i];
}
nums[index] = nums[index] * flag;
}
return -1; //error-case
}
//对于当前元素nums[i],将元素nums[Math.abs(nums[i])-1]加上已访问标志(将其乘以-1)
public int findDuplicate3(int[] nums) {
int length = nums.length;
for (int i=0; i<length; i++) {
int index = Math.abs(nums[i])-1;
if (nums[index]<0) { //已经被访问过了,说明Math.abs(nums[i])是重复元素
return Math.abs(nums[i]); //return index + 1;
}
nums[index] = -nums[index];
}
return 0; //error-case
}
//对于当前元素nums[i],将元素nums[nums[i]]进行交换,使得nums[index] = index
public int findDuplicate2(int[] nums) {
int duplicate = 0;
for (int i=0; i<nums.length; i++) {
while (nums[i] != i) {
if (nums[nums[i]] == nums[i]) {
duplicate = nums[i];
return duplicate;
}
int temp = nums[nums[i]];
nums[nums[i]] = nums[i];
nums[i] = temp;
}
}
return duplicate; //error-case
}
//对于当前元素nums[i],将其与元素nums[nums[i]-1]进行交换,使得nums[index] = index+1
public int findDuplicate1(int[] nums) {
for (int i=0; i<nums.length; ) {
if (nums[i] == i+1) {
i++;
continue;
}
if (nums[nums[i]-1] != nums[i]) { //nums[nums[i]]也可以
swap(nums, i, nums[i]-1);
} else {
return nums[i];
}
}
return -1; //error-case
}
private void swap(int[] ?nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public int findDuplicate_error(int[] nums) {
//[2,2,2,2,2] 输出6 期待2
int ret = 0;
for (int i=0; i<nums.length; i++) {
ret ^= nums[i];
if (ret == 0) {
return nums[i];
}
if (i != nums.length-1) {
ret ^= i+1;
}
}
return ret;
}
}
2、环检测
将元素值视为下一个待访问的元素的位置,由于数组肯定存在且只存在一个重复元素,访问序列肯定会不断重复一小段子序列。可以将其视为只带一个环的单链表(当前元素值是当前节点指向下一个节点的next指针),那么,问题就转变为找到环的入口点。
以测试用例[1, 3, 4, 2, 2]为例,访问序列为:1->3->2->4->2->4->...->2->4,环的入口点(元素2)就是其重复元素。
135 096 13213
class Solution {
public int findDuplicate4(int[] nums) {
int fast = 0, slow = 0; //快慢指针
//第一步:检测环(由于总有一个元素重复,环总存在)
slow = nums[slow];
fast = nums[nums[fast]];
while(nums[slow] != nums[fast]) { //快慢指针在环中总会相遇,退出循环时nums[slow]和nums[fast]就是相遇点的值
slow = nums[slow];
fast = nums[nums[fast]];
}
//第二步:算出环的入口点
fast = 0;
while(nums[fast] != nums[slow]) { //快慢指针再次相遇的点就是环的入口点————nums[fast]
fast = nums[fast];
slow = nums[slow];
}
return nums[fast];
}
}
3、
参考: