剑指 Offer 03. 数组中重复的数字
剑指 Offer 03. 数组中重复的数字
题目
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
限制:
2 <= n <= 100000
方法一:用HashSet
复杂度分析:
- 时间复杂度 O(N) : 遍历数组使用 O(N) ,HashSet 添加与查找元素皆为 O(1) 。
- 空间复杂度 O(N) : HashSet 占用 O(N) 大小的额外空间。
Code:
class Solution {
public int findRepeatNumber(int[] nums) {
int len = nums.length;
HashSet<Integer> set = new HashSet<>();
for (int x : nums) {
if (set.contains(x)) return x;
set.add(x);
}
return 0;
}
}
方法二:原地交换
思路:
题目说一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内,那么数组元素的索引和值应该是一对多的关系,因此,可遍历数组并通过交换操作,使元素的 索引 与 值 对应(即 nums[i]=i )
在遍历中,第一次遇到数字\(x\)时,将其交换到索引处\(x\);而当第二次遇到数字\(x\)时,一定已经有\(nums[x]=x\),此时的这个\(x\)就是多出来的
通俗易懂的解释:
这个原地交换法就相当于分配工作,每个索引代表一个工作岗位,每个岗位必须专业对口,既0索引必须0元素才能上岗。而我们的目的就是找出溢出的人才,既0索引岗位有多个0元素竞争。
我们先从0索引岗位开始遍历,首先我们看0索引是不是已经专业对口了,如果已经专业对口既nums[0]=0,那我们就跳过0岗位看1岗位。如果0索引没有专业对口,那么我们看现在0索引上的人才调整到他对应的岗位上,比如num[0]=2,那我们就把2这个元素挪到他对应的岗位上既num[2],这个时候有两种情况:1、num[2]岗位上已经有专业对口的人才了,既num[2]=2,这就说明刚刚那个在num[0]上的2是溢出的人才,我们直接将其返回即可。2、num[2]上的不是专业对口的人才,那我们将num[0]上的元素和num[2]上的元素交换,这样num[2]就找到专业对口的人才了。之后重复这个过程直到帮num[0]找到专业对口的人才,然后以此类推帮num[1]找人才、帮num[2]找人才,直到找到溢出的人才。
算法流程:
- 遍历数组nums,设初始索引值为i=0;
- 若 \(nums[i]=i\) 说明此数字已在对应索引位置,无需交换,因此跳过 ;
- 若\(nums[nums[i]]=nums[i]\):代表索引\(nums[i]\)处和索引\(i\)处的元素都为\(nums[i]\),找到一组重复值,返回该值\(nums[i]\);
- 否则:交换索引为\(i\)和\(nums[i]\)的元素值,将此数字交换至对应索引位置。
- 若遍历完毕尚未返回,则返回-1。
复杂度分析:
- 时间复杂度 O(N) : 遍历数组使用 O(N) ,每轮的判断和交换操作使用O(1)。
- 空间复杂度 O(1) : 使用常熟复杂度的额外空间。
Code:
class Solution {
public int findRepeatNumber(int[] nums) {
int len = nums.length;
int i = 0;
while (i < len) {
if (nums[i] == i) {
i++;
continue;
}
if (nums[nums[i]] == nums[i]) return nums[i];
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
return -1;
}
}

浙公网安备 33010602011771号