题目描述
- 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
- 地址:牛客地址
问题分析
- 使用额外空间:
- 用HashSet,遍历数组,看set中是否存在当前元素,若已经存在,那么该元素便是重复元素,直接返回。若不存在,降之存入Set
- 用数组,count[i] 表示 值为 i 的元素出现了几次。遍历到 i元素时,看count[i]是否为0,若不为0,说明以前出现过。若为0,次数加1。
时间复杂度:O(N) 空间复杂度:O(N)
- 不用额外空间:
- 注意题干,所有数字都是处于 0~n-1的范围内,所以可以在不使用额外空间的情况下,将 值为 i 的元素存到 数组的 i 位置处,使得 num[i] = i, 这便是一种 hash 的思想,如果数组总出现了重复元素,这两个元素都对应同一个 hash 地址,那么势必会引起冲突,发生冲突时便返回冲突元素即可,这便是数组中重复的数字。
- 那么如何调整数组,使 0~ n-1 装入对应位置中,使得 num[i] = i 呢?
从前到后遍历数组,看 num[i] 是否等于 i,若等于,则检测下一位置,若不等于,那么按照 hash规则, num[i] 应该放在 num[i] 处,即使得num[num[i]] = num[i],但对于该题,必须先检验 num[i] 位置处是否已经放置好了正确的元素,即 检测 num[num[i]] = num[i] 。
- 若等于,那么当前元素按规则也应该放在那位置,说明发生了冲突,出现了重复元素。直接返回该元素
- 若不等于,说明尚无冲突,那么 将 num[i] 放入 num[i] 位置,num[i] 位置上的原元素放在 i 位置,即交换 i 位置与 num[i] 位置上的元素。然后继续检验 num[i] 是否等于 i。直至等于,然后检查下一位置。
- 该方法直接改变原数组,并且对于每一个元素而讲,最多经历两次交换,便可处于正确位置,所以 时间复杂度:O(N) 空间复杂度:O(1)
经验教训
- 对于固定范围的数据,在固定空间下如何用 hash思想 来检重
- 如何调整数组,使 0~ n-1 装入对应位置中,使得 num[i] = i 呢?并且时间复杂度:O(N) 空间复杂度:O(1) 。有种连环怼的洗牌思想。
- 交换时出现的愚蠢的错误
代码实现
public boolean duplicate1(int numbers[],int length,int [] duplication) {
if(numbers == null || length == 0 || length == 1) {
return false;
}
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < length; i++) {
if (set.contains(numbers[i])) {
duplication[0] = numbers[i];
return true;
}
set.add(numbers[i]);
}
return false;
}
public boolean duplicate(int numbers[],int length,int [] duplication) {
if(numbers == null || length <= 1) {
return false;
}
for (int i = 0; i < length; i++) {
while (numbers[i] != i) {
if (numbers[numbers[i]] == numbers[i]) {
duplication[0] = numbers[i];
return true;
}
int temp = numbers[i];
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}