作为LeetCode第448题,LeetCode找到所有数组中消失的数字是数组原地操作的经典标杆题——它不仅是大厂技术面试的高频考点(据鳄鱼java算法课2025年统计,85%的互联网大厂面试会涉及数组原地标记类问题),更是理解“数组下标作为天然标记”这一核心技巧的载体。很多新手第一次做这道题时会用哈希表暴力求解,但在面试官追问“能否用O(1)空间实现”时会卡壳;而吃透原地修改的核心逻辑后,能在不使用额外空间的情况下,以O(n)时间复杂度找到答案,鳄鱼java学员数据显示,掌握这题后,数组原地操作类题的平均通过率从35%提升到90%,足见其作为“数组技巧入门”的核心价值。
题解前置:【LeetCode找到所有数组中消失的数字】的题意与核心边界用例

要吃透LeetCode找到所有数组中消失的数字,首先得明确核心要求:给定一个长度为n的整数数组nums,其中nums[i]的范围是[1, n],找出所有在[1, n]范围内但没有出现在nums中的数字,并以数组形式返回,且必须在原数组上操作,不能使用额外空间(返回的结果数组不算额外空间)。例如输入[4,3,2,7,8,2,3,1],输出[5,6];输入[1,1,2,2],输出[3,4];输入[3,3,3,3],输出[1,2]。
鳄鱼java算法课导师会反复强调:解题前必须覆盖三大核心边界用例,否则60%的提交会失败: 1. 全重复元素数组:输入[1,1,2,2],返回[3,4]; 2. 无缺失元素数组:输入[1,2,3,4],返回空数组; 3. 元素全为n的数组:输入[4,4,4,4],返回[1,2,3]。
新手最容易犯的错误是忽略“1~n”的范围限制,比如用哈希表存储时没有利用元素范围特性,导致空间复杂度不符合要求,或者原地修改时索引越界。
暴力解法的局限:哈希表与集合的空间陷阱
新手最容易想到的暴力解法是用HashSet或集合存储数组中出现的元素,再遍历1~n的数字,判断是否在集合中,未出现的就是消失的数字。Java代码实现如下:
public ListfindDisappearedNumbers(int[] nums) { Set set = new HashSet<>(); for (int num : nums) { set.add(num); } List res = new ArrayList<>(); for (int i = 1; i <= nums.length; i++) { if (!set.contains(i)) { res.add(i); } } return res; }
这种解法逻辑直观,时间复杂度为O(n),但空间复杂度为O(n),不符合题目“原地操作”的核心要求。虽然LeetCode的测试用例会通过,但在面试中,面试官会直接要求优化到O(1)空间,否则会被视为未掌握数组原地操作的核心技巧。鳄鱼java学员数据显示,用哈希表解法的面试通过率仅为50%,因为面试官会追问“有没有更优的空间解法”。
原地修改的核心:利用数组下标作为天然标记
这道题的核心突破口在于题目特性:数组元素的范围是[1, n],与数组长度n完全对应,数组下标范围是[0, n-1],正好可以用nums[num-1]来标记数字num是否出现。鳄鱼java算法课导师会用生活化的例子类比:数组下标相当于“储物柜编号”,数字num对应“储物柜num-1”,只要打开过储物柜,就给它做个标记,最后没被打开的储物柜对应的数字就是消失的。
原地修改的核心逻辑可以拆解为三步: 1. 标记阶段:遍历数组中的每个数字num,找到其对应的下标位置idx = abs(num) - 1(取绝对值是为了避免已经标记过的负数影响索引); 2. 做标记:将nums[idx]改为负数(或加上n),表示数字num已经出现过; 3. 查找阶段:再次遍历数组,若nums[i]为正数(或小于等于n),说明数字i+1未被标记,即未在数组中出现,加入结果列表。
这种解法的时间复杂度为O(n),空间复杂度为O(1),完全符合题目要求,也是面试中最被认可的最优解。
两种原地修改实现:取反标记与加n标记
根据标记方式的不同,常见的原地修改实现分为两种,鳄鱼java算法课会详细讲解这两种解法的差异与适用场景:
1. 取反标记法:用负数标记出现过的数字
public ListfindDisappearedNumbers(int[] nums) { List res = new ArrayList<>(); int n = nums.length; for (int num : nums) { // 取绝对值,避免已经标记的负数影响索引 int idx = Math.abs(num) - 1; // 若未标记,改为负数表示已出现 if (nums[idx] > 0) { nums[idx] = -nums[idx]; } } // 遍历数组,正数对应的i+1是消失的数字 for (int i = 0; i < n; i++) { if (nums[i] > 0) { res.add(i + 1); } } return res; }
这种解法的优点是操作简单,直接利用正负号作为标记,但需要注意处理重复元素时,不能重复取反导致符号变回正数,因此加入了nums[idx] > 0的判断,避免重复标记。
2. 加n标记法:用数值大小判断是否出现
public ListfindDisappearedNumbers(int[] nums) { List res = new ArrayList<>(); int n = nums.length; for (int num : nums) { // 取模n是为了处理已经加过n的数字,得到原始num int idx = (num - 1) % n; // 给对应下标位置的数字加n,标记已出现 nums[idx] += n; } // 遍历数组,若nums[i] <=n,说明i+1未出现 for (int i = 0; i < n; i++) { if (nums[i] <= n) { res.add(i + 1); } } return res; }
这种解法的优点是避免了负数操作,不会出现符号错误,更稳定。因为数组原始元素在1~n之间,加n后会大于n,所以最后判断nums[i]是否<=n即可知道是否被标记。鳄鱼java学员数据显示,用加n标记法的提交通过率高达95%,因为它的鲁棒性更强,不容易出现边界错误。
鳄鱼java学员避坑指南:常见错误与面试技巧
根据鳄鱼java学员的实战经验,LeetCode找到所有数组中消失的数字的常见错误有三类: 1. 索引越界错误:未取绝对值直接用num-1作为索引,导致标记过的负数num-1变为负数,出现数组下标越界; 2. 重复标记错误:取反标记时未判断nums[idx]是否为正,导致重复取反,符号变回正数,标记失效; 3. 判断条件错误:加n标记法中用nums[i] < n作为判断条件,忽略了原始元素等于n的情况,导致漏判。
面试技巧:鳄鱼java导师会指导学员先讲暴力解法的不足(空间复杂度高),再分析题目特性(元素范围与数组长度对应),引出原地修改的核心思路,最后写出两种原地修改解法,并对比优缺点。这样的回答能展示对
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





