剑指03-数组中重复的数字
一、原题题目
1.1 题目
找出数组中重复的数字。在一个长度为 n
的数组 nums
里的所有数字都在 0~n-1
的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
1.2 示例
示例1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
提示:
- 可以假设
s
和t
长度相同。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcofhttps://leetcode-cn.com/problems/isomorphic-strings/)
二、解题思路
2.1 利用【暴力遍历】解题
- 解题思路
题目的要求是找到重复的数字,那么如果我们用暴力遍历的方法,第一个数字和后面的比较,如果没有重复就 i++
,然后继续,这种方法需要时间复杂度是 o(n^2)
,空间复杂度是 o(n)
,显然时间复杂度太高了,那么我们就会想如何才能减少时间复杂度来找到重复的数字呢?
- 详细代码(Java)
public int findRepeatNumber(int[] nums) {
for (int i = 0 ;i<nums.length;i++){
for (int j = i+1;j<nums.length;j++){
if (nums[i]==nums[j])
return nums[i];
}
}
return 0;
}
- 代码执行结果
执行结果理所当然的超时,我们一般这种降低时间复杂度的方法就是,通过空间换时间,就是例如哈希表或者集合,把遍历过的值记录下来,这样就可以节约大量的时间,所以方法二就是使用哈希表把遍历过的元素存下来,如果发现一次就记录1,发现两次就记录2,并且每次遍历检查,如果大于等于2了,就返回该重复的数字,这种方法很好理解,也是大多数人会使用的方法,这种方法的好处在于把时间复杂度降低到了o(n),但同样付出的代价是空间复杂度上升到了o(n),这是很典型的空间换时间的题目,同样的题目,如两数之和也是类似。
2.2 利用【空间换时间】提升性能
- 解题思路
可以利用哈希表,数组,集合等来记录每个数组出现的情况,从而判断数组内的数字是否有重复,最后发现用数组记录的时间复杂度最低。
- 详细代码(Java):哈希表
public int findRepeatNumber(int[] nums) {
HashMap<Integer,Integer> map = new HashMap<>();
for (int i = 0;i <nums.length;i++){
if (map.containsKey(nums[i])) return nums[i];
map.put(nums[i],1);
}
return 0;
}
- 哈希表代码执行结果

- 详细代码(Java):集合
public int findRepeatNumber(int[] nums) {
Set<Integer> set = new HashSet<Integer>();
for (int x : nums){
if (!set.add(x)) return x;
}
return 0;
}
- 代码执行结果

- 详细代码(Java):数组
public int findRepeatNumber(int[] nums) {
boolean [] num = new boolean[nums.length];
for (int i = 0; i < nums.length; i++){
if (num[nums[i]] == false){
num[nums[i]] = true;
}else {
return nums[i];
}
}
return 0;
}
- 代码执行结果

2.3 利用【排序】解题
- 解题思路
再想提升性能可能就要在空间上下注意了,能不能原数组上实现题目要求呢?应该是有的,第一种方法我想到的是先对数组排序,如果存在两个连续的数字相同不就是重复的了嘛。
- 详细代码(Java)
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i] == nums[i + 1]) return nums[i];
}
return 0;
}
- 代码执行结果

2.3 【原地置换】解题
- 解题思路
因为题目告诉我们的信息中是数字只会小于数组的个数,那么也就是说,如果没有重复的,那么每个数组应该都可以对应到一个下标位置。具体可以理解为一个萝卜一个坑。用下面的例子来看更直接。
nums[i] 3 1 0 2 萝卜
i 0 1 2 3 坑
0号坑说我要的是0号萝卜,不要3号萝卜,所以会去和3号坑的萝卜交换,因为如果0号坑拿了3号坑的3号萝卜,那说明3号坑装的也肯定是别人家的萝卜,所以要跟3号坑换,换完是这样的:
nums[i] 2 1 0 3 萝卜
i 0 1 2 3 坑
然而0号坑还没找到自己的萝卜,它不要2号萝卜,又去和2号坑的萝卜交换,换完是这样的:
nums[i] 0 1 2 3 萝卜
i 0 1 2 3 坑
这时候刚好就是一一对应的,交换过程也不会出现不同坑有相同编号的萝卜。要注意交换用的是while,也就是0号坑只有拿到0号萝卜,1号坑才能开始找自己的萝卜。
如果有重复元素,例如:
nums[i] 1 2 3 2 萝卜
i 0 1 2 3 坑
同样的,0号坑不要1号,先和1号坑交换,交换完这样的:
nums[i] 2 1 3 2 萝卜
i 0 1 2 3 坑
0号坑不要2号萝卜,去和2号坑交换,交换完这样的:
nums[i] 3 1 2 2 萝卜
i 0 1 2 3 坑
0号坑不要3号萝卜,去和3号坑交换,交换完这样的:
nums[i] 2 1 2 3 萝卜
i 0 1 2 3 坑
0号坑不要2号萝卜,去和2号坑交换,结果发现你2号坑也是2号萝卜,那我还换个锤子,同时也说明有重复元素出现。
- 详细代码(Java)
public int findRepeatNumber(int[] nums) {
int temp; // 定义临时交换变量
for (int i = 0;i<nums.length;i++){ // 从第0个坑位开始找对应的萝卜
while (nums[i]!=i){ // 当前坑位不是自己对应的萝卜 而是nums[i]坑位的
// 如果发现nums[i]坑位的萝卜是它自己坑位的,说明自己的是重复多的
if (nums[i]==nums[nums[i]])
return nums[i];
// 如果发现nums[i]坑位的萝卜不是它自己坑位的,先交换过来再继续去找
temp = nums[i];
nums[i]=nums[temp];
nums[temp] = temp;
}
}
return 0;
}
- 代码执行结果
