文章目录
题目
标题和出处
标题:错误的集合
出处:645. 错误的集合
难度
3 级
题目描述
要求
集合 s \texttt{s} s 包含从 1 \texttt{1} 1 到 n \texttt{n} n 的整数。不幸的是,因为数据错误,集合中某一个数字复制成了集合中的另外一个数字,导致集合有一个数字重复 并且丢失了一个数字。
给定一个整数数组 nums \texttt{nums} nums,代表集合发生错误后的结果。
找到出现两次的整数和丢失的整数,将它们以数组的形式返回。
示例
示例 1:
输入: nums = [1,2,2,4] \texttt{nums = [1,2,2,4]} nums = [1,2,2,4]
输出: [2,3] \texttt{[2,3]} [2,3]
示例 2:
输入: nums = [1,1] \texttt{nums = [1,1]} nums = [1,1]
输出: [1,2] \texttt{[1,2]} [1,2]
数据范围
- 2 ≤ nums.length ≤ 10 4 \texttt{2} \le \texttt{nums.length} \le \texttt{10}^\texttt{4} 2≤nums.length≤104
- 1 ≤ nums[i] ≤ 10 4 \texttt{1} \le \texttt{nums[i]} \le \texttt{10}^\texttt{4} 1≤nums[i]≤104
解法一
思路和算法
首先将数组 nums \textit{nums} nums 升序排序,然后遍历数组寻找重复的数字和丢失的数字。
如果所有数字都是正确的,则在排序之后,长度为 n n n 的数组中的每个下标 i i i 都满足 nums [ i ] = i + 1 \textit{nums}[i] = i + 1 nums[i]=i+1,当 i > 0 i > 0 i>0 时有 nums [ i ] − nums [ i − 1 ] = 1 \textit{nums}[i] - \textit{nums}[i - 1] = 1 nums[i]−nums[i−1]=1。如果规定 nums [ − 1 ] = 0 \textit{nums}[-1] = 0 nums[−1]=0,则对于每个下标都有 nums [ i ] − nums [ i − 1 ] = 1 \textit{nums}[i] - \textit{nums}[i - 1] = 1 nums[i]−nums[i−1]=1。因此,遍历数组并计算每一对相邻元素之差,即可找到重复的数字和丢失的数字。用 curr \textit{curr} curr 和 prev \textit{prev} prev 分别表示当前数字和上一个数字,初始时 prev = 0 \textit{prev} = 0 prev=0。
-
如果 curr − prev = 0 \textit{curr} - \textit{prev} = 0 curr−prev=0,则 curr \textit{curr} curr 为重复的数字。
-
如果 curr − prev > 1 \textit{curr} - \textit{prev} > 1 curr−prev>1,则 curr \textit{curr} curr 和 prev \textit{prev} prev 之间的数字为丢失的数字,即 curr − 1 \textit{curr} - 1 curr−1 为丢失的数字。
重复的数字一定在数组中出现两次,因此遍历数组一定可以找到重复的数字。当丢失的数字不是 n n n 时,遍历数组可以找到丢失的数字(包括丢失的数字是 1 1 1 也可以找到,因为 prev \textit{prev} prev 的初始值是 0 0 0),但是当丢失的数字是 n n n 时,数组中不存在相邻元素之差等于 2 2 2,因此在遍历结束之后需要根据 nums [ n − 1 ] \textit{nums}[n - 1] nums[n−1] 的值判断丢失的数字是否是 n n n,当 nums [ n − 1 ] ≠ n \textit{nums}[n - 1] \ne n nums[n−1]=n 时,丢失的数字是 n n n。
代码
java
class Solution {
public int[] findErrorNums(int[] nums) {
int[] errorNums = new int[2];
Arrays.sort(nums);
int n = nums.length;
int prev = 0;
for (int i = 0; i < n; i++) {
int curr = nums[i];
int difference = curr - prev;
if (difference == 0) {
errorNums[0] = curr;
} else if (difference != 1) {
errorNums[1] = curr - 1;
}
prev = curr;
}
if (nums[n - 1] != n) {
errorNums[1] = n;
}
return errorNums;
}
}
复杂度分析
-
时间复杂度: O ( n log n ) O(n \log n) O(nlogn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( n log n ) O(n \log n) O(nlogn) 的时间,遍历数组需要 O ( n ) O(n) O(n) 的时间,总时间复杂度是 O ( n log n ) O(n \log n) O(nlogn)。
-
空间复杂度: O ( log n ) O(\log n) O(logn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( log n ) O(\log n) O(logn) 的递归调用栈空间。
解法二
思路和算法
为了找到丢失的数字,可以使用哈希表记录数组中每个数字的出现次数,然后寻找重复的数字和丢失的数字。
由于所有的数字都在范围 [ 1 , n ] [1, n] [1,n] 内,因此可以使用长度为 n + 1 n + 1 n+1 的数组代替哈希表记录每个数字的出现次数。得到每个数字的出现次数之后,依次遍历范围 [ 1 , n ] [1, n] [1,n] 内的每个数字,出现 2 2 2 次的数字是重复的数字,出现 0 0 0 次的数字是丢失的数字。
代码
java
class Solution {
public int[] findErrorNums(int[] nums) {
int[] errorNums = new int[2];
int n = nums.length;
int[] counts = new int[n + 1];
for (int num : nums) {
counts[num]++;
}
for (int i = 1; i <= n; i++) {
if (counts[i] == 2) {
errorNums[0] = i;
} else if (counts[i] == 0) {
errorNums[1] = i;
}
}
return errorNums;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次获得数组中的所有数字的出现次数,然后需要遍历范围 [ 1 , n ] [1, n] [1,n] 内的每个数字寻找丢失的数字。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。哈希表需要 O ( n ) O(n) O(n) 的空间。
解法三
思路和算法
可以使用原地哈希将空间复杂度降低到 O ( 1 ) O(1) O(1),并保持时间复杂度 O ( n ) O(n) O(n)。
原地哈希的思想是将每个数字都交换到正确的下标位置。如果所有数字都是正确的,则在经过原地哈希交换之后,对于每个下标 i i i 都有 nums [ i ] = i + 1 \textit{nums}[i] = i + 1 nums[i]=i+1。假设重复的数字是 j j j,丢失的数字是 k k k,则在经过原地哈希交换之后, nums [ k − 1 ] = j \textit{nums}[k - 1] = j nums[k−1]=j,数组中其余的每个下标处的元素都是正确的。
具体做法是,对于数组 nums \textit{nums} nums 的每个下标 i i i,如果 nums [ i ] ≠ i + 1 \textit{nums}[i] \ne i + 1 nums[i]=i+1 且 nums [ i ] ≠ nums [ nums [ i ] − 1 ] \textit{nums}[i] \ne \textit{nums}[\textit{nums}[i] - 1] nums[i]=nums[nums[i]−1],则下标 nums [ i − 1 ] \textit{nums}[i - 1] nums[i−1] 与下标 i i i 处的元素不相等,将下标 nums [ i − 1 ] \textit{nums}[i - 1] nums[i−1] 与下标 i i i 处的元素交换,交换之后 nums [ i − 1 ] = i \textit{nums}[i - 1] = i nums[i−1]=i,重复该过程直到 nums [ i ] = i + 1 \textit{nums}[i] = i + 1 nums[i]=i+1 或 nums [ i ] = nums [ nums [ i ] − 1 ] \textit{nums}[i] = \textit{nums}[\textit{nums}[i] - 1] nums[i]=nums[nums[i]−1],此时下标 i i i 处的元素无法继续交换。
当每个下标的交换操作都结束之后,遍历数组 nums \textit{nums} nums。如果遇到 nums [ i ] ≠ i + 1 \textit{nums}[i] \ne i + 1 nums[i]=i+1,则 nums [ i ] \textit{nums}[i] nums[i] 是重复的数字, i + 1 i + 1 i+1 是丢失的数字。
代码
java
class Solution {
public int[] findErrorNums(int[] nums) {
int[] errorNums = new int[2];
int n = nums.length;
for (int i = 0; i < n; i++) {
while (nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) {
swap(nums, nums[i] - 1, i);
}
}
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
errorNums[0] = nums[i];
errorNums[1] = i + 1;
break;
}
}
return errorNums;
}
public void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次,每次会将一个元素交换到正确的下标位置,因此每个元素的访问次数是常数次,时间复杂度是 O ( n ) O(n) O(n)。
-
空间复杂度: O ( 1 ) O(1) O(1)。