LeetCode 448. 找到所有数组中消失的数字
题目描述

题意分析
数组长度为
n,每个数都在1..n中,找出这个范围内所有没有出现的值。数组可能重复,同一个值出现多少次不影响是否缺失;返回缺失数值,不是缺失位置的下标。进阶要求线性时间、常数额外空间,返回结果不计入额外空间。当前解法借用输入数组的符号记录出现状态,会修改数组,但不会丢失各位置原值的绝对值。
解法:利用下标原地标记出现过的数字
核心思路
[!blue]
值域正好有
n个可能值,可以给每个值指定一个标记位置:值v对应下标v - 1。若使用额外布尔数组就能记录是否出现,而本题输入全为正数,原数组的符号也可以承担这个标记。遍历到原值
v时,将nums[v - 1]设为负数,表示值v已出现。这里只改变正负号,不改变大小,因此被标记位置之后轮到作为输入读取时,可以用绝对值恢复它原来保存的数值,再继续为这个原值设置标记。同一个值可能出现多次,标记操作必须幂等:目标位置已经为负就不再变化,只在仍为正时取负。不能每次都翻转,否则重复出现会把“见过”状态擦掉。
第一遍完成后,下标
i为负当且仅当原数组出现过值i + 1。所以第二遍只需收集仍为正的位置对应的i + 1。必须等全部标记结束再收集,因为尚未扫描的后面仍可能出现某个值。
解题步骤
- 遍历输入,先对当前位置取绝对值,得到原值
v。- 映射到下标
v - 1,若该位置为正就取负,已经为负则保持。- 第一遍完成后重新扫描数组,将每个正数位置对应的
i + 1加入答案。- 返回结果,负号标记会保留在输入数组中。
代码实现
class Solution {
// 可以把原数组当成访问标记表:看到值 value 时,将 nums[value - 1] 置为负数。
public List<Integer> findDisappearedNumbers(int[] nums) {
for (int num : nums) {
// 当前位置可能已经取负,先恢复原值再映射下标
int idx = Math.abs(num) - 1;
// 重复值只保留负标记,不将它再次翻回正数
if (nums[idx] > 0) {
nums[idx] = -nums[idx];
}
}
List<Integer> res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
res.add(i + 1);
}
}
return res;
}
}
func findDisappearedNumbers(nums []int) []int {
// 可以把原数组当成访问标记表:看到值 value 时,将 nums[value - 1] 置为负数。
for i := 0; i < len(nums); i++ {
// 当前位置可能已经取负,先恢复原值再映射下标
idx := nums[i]
if idx < 0 {
idx = -idx
}
idx--
// 重复值只保留负标记,不将它再次翻回正数
if nums[idx] > 0 {
nums[idx] = -nums[idx]
}
}
res := make([]int, 0)
for i, val := range nums {
if val > 0 {
res = append(res, i+1)
}
}
return res
}
复杂度分析
- 时间复杂度:$O(n)$,两次线性扫描。
- 空间复杂度:$O(1)$,借用输入符号记录状态,结果列表另计。
关键点总结
[!green]
- 当前元素的绝对值用来继续读取原输入,当前下标的符号用来表示对应值是否出现,二者不是同一层含义。
- 值域
1..n保证映射合法,原值全正保证正负号可以区分状态。- 同一标记只从正变负,不因重复值再次翻回。
- 返回数值
i + 1,而不是存放标记的零基下标。
易错点总结
[!yellow]
- 读取已被标记的输入时不取绝对值,会用负数计算非法下标。
- 每次遇到重复值都翻转符号,会让偶数次出现的值错误地变成未出现。
- 标记尚未全部完成就收集答案,可能把后面将出现的值误报为缺失。
- 映射时忘记减一或输出时忘记加一,会混淆值域与数组下标。
- 本实现会修改输入,不能把它描述为只读扫描。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 442. 数组中重复的数据 | 中等 | 出现标记相同,原题在重复命中时收集值,本题在标记结束后找未出现位置。 |
| 41. 缺失的第一个正数 | 困难 | 本题值域已在1到n,原题还需忽略非正数和超范围值,再寻找最小缺失正数。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!