LeetCode找到所有数组中消失的数字:从暴力到原地修改,吃透数组标记技巧

admin 2026-02-09 阅读:17 评论:0
作为LeetCode第448题,LeetCode找到所有数组中消失的数字是数组原地操作的经典标杆题——它不仅是大厂技术面试的高频考点(据鳄鱼java算法课2025年统计,85%的互联网大厂面试会涉及数组原地标记类问题),更是理解“数组下标作...

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

题解前置:【LeetCode找到所有数组中消失的数字】的题意与核心边界用例

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 List findDisappearedNumbers(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 List findDisappearedNumbers(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 List findDisappearedNumbers(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导师会指导学员先讲暴力解法的不足(空间复杂度高),再分析题目特性(元素范围与数组长度对应),引出原地修改的核心思路,最后写出两种原地修改解法,并对比优缺点。这样的回答能展示对

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

分享:

扫一扫在手机阅读、分享本文

热门文章
  • 多线程破局:KeyDB如何重塑Redis性能天花板?

    多线程破局:KeyDB如何重塑Redis性能天花板?
    在Redis以其卓越的性能和丰富的数据结构统治内存数据存储领域十余年后,其单线程事件循环模型在多核CPU成为标配的今天,逐渐显露出性能扩展的“阿喀琉斯之踵”。正是在此背景下,KeyDB多线程Redis替代方案现状成为了一个极具探讨价值的技术议题。深入剖析这一现状,其核心价值在于为面临性能瓶颈、寻求更高吞吐量与更低延迟的开发者与架构师,提供一个经过生产验证的、完全兼容Redis协议的多线程解决方案的全面评估。这不仅是关于一个“分支”项目的介绍,更是对“Redis单线程哲学”与“...
  • 拆解数据洪流:ShardingSphere分库分表实战全解析

    拆解数据洪流:ShardingSphere分库分表实战全解析
    拆解数据洪流:ShardingSphere分库分表实战全解析 当单表数据量突破千万、数据库连接成为瓶颈时,分库分表从可选项变为必选项。然而,如何在不重写业务逻辑的前提下,平滑、透明地实现数据水平拆分,是架构升级的核心挑战。一次完整的MySQL分库分表ShardingSphere实战案例,其核心价值在于掌握如何通过成熟的中间件生态,将复杂的分布式数据路由、事务管理和SQL改写等难题封装化,使开发人员能像操作单库单表一样处理海量数据,从而在不影响业务快速迭代的前提下,实现数据库能...
  • 提升可读性还是制造混乱?深度解析Java var的正确使用场景

    提升可读性还是制造混乱?深度解析Java var的正确使用场景
    自JDK 10引入以来,var关键字无疑是最具争议又最受开发者欢迎的语法特性之一。它允许编译器根据初始化表达式推断局部变量的类型,从而省略显式的类型声明。Java Var局部变量类型推断使用场景的探讨,其核心价值远不止于“少打几个字”,而是如何在减少代码冗余与维持代码清晰度之间找到最佳平衡点。理解其设计哲学和最佳实践,是避免滥用、真正发挥其提升开发效率和代码可读性作用的关键。本文将系统性地剖析var的适用边界、潜在陷阱及团队规范,为你提供一份清晰的“作战地图”。 一、var的...
  • ConcurrentHashMap线程安全实现原理:从1.7到1.8的进化与实战指南

    ConcurrentHashMap线程安全实现原理:从1.7到1.8的进化与实战指南
    在Java后端高并发场景中,线程安全的Map容器是保障数据一致性的核心组件。Hashtable因全表锁导致性能极低,Collections.synchronizedMap仅对HashMap做了简单的同步包装,无法满足万级以上并发需求。【ConcurrentHashMap线程安全实现原理】的核心价值,就在于它通过不同版本的锁机制优化,在保证线程安全的同时实现了极高的并发性能——据鳄鱼java社区2026年性能测试数据,10000并发下ConcurrentHashMap的QPS是...
  • 2026重庆房地产税最新政策解读:起征点31528元/㎡+免税面积180㎡,影响哪些购房者?

    2026重庆房地产税最新政策解读:起征点31528元/㎡+免税面积180㎡,影响哪些购房者?
    2026年重庆房地产税政策迎来新一轮调整,精准把握政策细节对购房者、多套房业主及投资者至关重要。重庆 2026 房地产税最新政策解读的核心价值在于:清晰拆解征收范围、税率标准、免税规则等关键变化,通过具体案例计算纳税金额,帮助市民判断自身税负,提前规划房产配置。据鳄鱼java房产数据平台统计,2026年重庆房产税起征点较2025年上调8.2%,政策调整后约65%的存量住房可享受免税或低税率优惠,而未及时了解政策的业主可能面临多缴税费风险。本文结合重庆市住建委2026年1月最新...
标签列表