1、问题描述
给你一个含 n
个整数的数组 nums
,其中 nums[i]
在区间 [1, n]
内。请你找出所有在 [1, n]
范围内但没有出现在 nums
中的数字,并以数组的形式返回结果。
示例 1:
输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
示例 2:
输入:nums = [1,1]
输出:[2]
提示:
-
n == nums.length
-
1 <= n <= 105
-
1 <= nums[i] <= n
进阶:你能在不使用额外空间且时间复杂度为 O(n)
的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。
2、解题思路
方法1:哈希表标记法(findDisappearedNumbers1
)
核心思想:
-
使用
HashMap
记录数字是否出现,key
为数字,value
为0
(未出现)或1
(出现)。 -
遍历
1
到n
,检查HashMap
中值为0
的数字,即为消失的数字。
步骤:
-
初始化哈希表:
-
遍历
1
到n
,将所有数字的value
初始化为0
。
-
-
标记出现的数字:
-
遍历数组
nums
,将出现的数字value
设为1
。
-
-
收集消失的数字:
-
再次遍历
1
到n
,若map.get(i) == 0
,则i
是消失的数字。
-
复杂度分析:
-
时间复杂度:O(n)(三次遍历)。
-
空间复杂度:O(n)(需额外
HashMap
存储)。
适用场景:
-
允许使用额外空间时,逻辑清晰,但空间效率低。
//使用hashmap来记录不存在的数字为0,存在的数字为1
public static List<Integer> findDisappearedNumbers1(int[] nums){
Map<Integer,Integer> map = new HashMap<>();
int len = nums.length;//8
List<Integer> result = new ArrayList<>();
for (int i = 0; i <= len; i++) {
map.put(i, 0);
}
for (int i = 0; i < len; i++) {
map.put(nums[i], 1);
}
for (int i = 1,j=0; i <= len; i++) {
if (map.get(i) == 0){
result.add(i);
}
}
return result;
}
方法2:原地标记法(findDisappearedNumbers2
)
核心思想:
-
利用数组下标和符号标记数字是否出现,不占用额外空间。
-
遍历数组,将
nums[i]
对应的索引处的值设为负数,表示该数字已出现。 -
最后检查未被标记的索引,即为消失的数字。
步骤:
-
第一次遍历(标记):
-
对每个
nums[i]
,计算index = abs(nums[i]) - 1
(防止负数干扰)。 -
若
nums[index] > 0
,则将其设为负数(标记为已出现)。
-
-
第二次遍历(收集结果):
-
检查所有
nums[i]
,若nums[i] > 0
,说明i + 1
未出现过。
-
复杂度分析:
-
时间复杂度:O(n)(两次遍历)。
-
空间复杂度:O(1)(原地修改,仅返回结果占用空间)。
适用场景:
-
要求空间复杂度为 O(1) 时的高效解法。
//进阶:使用负数,不适用额外空间
public static List<Integer> findDisappearedNumbers2(int[] nums){
int len = nums.length;
List<Integer> result = new ArrayList<>();
for (int i = 0; i < len; i++) {
int index = Math.abs(nums[i]) - 1; // 计算正确的索引(避免负数影响)
if (nums[index] > 0) {
nums[index] = -nums[index]; // 标记为负数
}
}
for (int i = 0; i < len; i++) {
if (nums[i] < 0) continue;
result.add(i + 1);
}
return result;
}
方法3:加法标记法(findDisappearedNumbers3
)
核心思想:
-
通过加法标记数字是否出现,避免符号冲突(适用于元素全为正数的场景)。
-
遍历数组,将
nums[i]
对应的索引处的值加上n
(数组长度),标记该数字已出现。 -
最后检查
nums[i] <= n
的索引,即为消失的数字。
步骤:
-
第一次遍历(标记):
-
计算
index = (nums[i] - 1) % n
(防止索引越界)。 -
若
nums[index] <= n
,则nums[index] += n
(标记为已出现)。
-
-
第二次遍历(收集结果):
-
检查所有
nums[i]
,若nums[i] <= n
,说明i + 1
未出现过。
-
复杂度分析:
-
时间复杂度:O(n)(两次遍历)。
-
空间复杂度:O(1)(原地修改)。
适用场景:
-
当数组元素全为正数且不允许修改符号时(比方法2更通用)。
//进阶:使用加法
public static List<Integer> findDisappearedNumbers3(int[] nums){
int len = nums.length;
List<Integer> result = new ArrayList<>();
for (int i = 0; i < len; i++) {
int index = (nums[i] - 1) % 8;//还原数组,因为会出现重复的下标
if(nums[index] <= len){
nums[index] += len;
}
}
for (int i = 0; i < len; i++) {
if(nums[i] <= len){
result.add(i + 1);
}
}
return result;
}