448. 找到所有数组中消失的数字
题目描述
给你一个含 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,数字范围是 1~n,每个数字出现 1 次或 2 次。需要找出没出现的数字。关键问题是:如何在不使用额外空间的情况下标记数字是否出现?
第二步:哈希表解法
使用集合记录出现的数字,然后遍历 1~n 找出缺失的。
缺点:需要 O(n) 额外空间。
第三步:原地哈希(最优)
核心洞察:
- 数字
num对应索引num - 1 - 将出现过的数字对应位置的值增加
n - 最后值仍
<= n的位置就是没出现过的数字
为什么正确:
- 每个
num会让nums[num - 1]增加n - 原本的值范围是
1~n,增加后> n的位置表示该数字出现过 - 使用
% n是为了防止多次加n后索引计算出错
完整代码实现
from typing import List
class Solution:
"""
找到所有数组中消失的数字 - 原地哈希
将出现过的数字对应位置的值增加 n
值仍 <= n 的位置就是没出现过的数字
时间复杂度:O(n)
空间复杂度:O(1)(不计输出空间)
"""
def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
n = len(nums)
for num in nums:
x = (num - 1) % n # 计算该数字对应的索引
nums[x] += n # 标记该数字出现过
# 值仍 <= n 的位置,说明对应的数字(i+1)没有出现过
return [i + 1 for i, num in enumerate(nums) if num <= n]
示例推演
以 nums = [4,3,2,7,8,2,3,1] 为例,n=8:
| 步骤 | num | x=(num-1)%8 | 操作 | nums 状态 |
|---|---|---|---|---|
| 初始 | - | - | - | [4,3,2,7,8,2,3,1] |
| 1 | 4 | 3 | nums[3]+=8 | [4,3,2,15,8,2,3,1] |
| 2 | 3 | 2 | nums[2]+=8 | [4,3,10,15,8,2,3,1] |
| 3 | 2 | 1 | nums[1]+=8 | [4,11,10,15,8,2,3,1] |
| 4 | 7 | 6 | nums[6]+=8 | [4,11,10,15,8,2,11,1] |
| 5 | 8 | 7 | nums[7]+=8 | [4,11,10,15,8,2,11,9] |
| 6 | 2 | 1 | nums[1]+=8 | [4,19,10,15,8,2,11,9] |
| 7 | 3 | 2 | nums[2]+=8 | [4,19,18,15,8,2,11,9] |
| 8 | 1 | 0 | nums[0]+=8 | [12,19,18,15,8,2,11,9] |
最终检查:索引 4 和 5 的值 8 和 2 都 <= 8,对应数字 5 和 6。
答案:[5, 6]
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 哈希表 | O(n) | O(n) | 使用额外集合 |
| 原地哈希(最优) | O(n) | O(1) | 原地修改数组 |
易错点总结
1. 使用 % n
原因:某些数字可能出现两次,导致 nums[x] 增加多次 n。使用 % n 确保索引计算正确。
2. 原数组被修改
原地哈希会修改输入数组,如果后续还需要使用原数组,需要先复制。