当前位置: 首页 > news >正文

力扣hot100:缺失的第一个正数(哈希思想)(41)

题目描述:
解题思路

核心思路是通过原地标记实现空间复杂度 O(1)。缺失的最小正整数一定在 [1, n+1] 范围内(n 是数组长度),因此只需关注这个范围内的数。分三步操作:

  1. 预处理非正数:将所有非正数(负数和零)替换为 n+1,避免干扰后续标记。
  2. 标记出现过的数:遍历数组,对每个值 x,若 x ∈ [1, n],将位置 x-1 的值标记为负数(表示 x 出现过)。
  3. 查找缺失数:再次遍历数组,第一个正数所在位置 i 表示 i+1 是缺失的最小正整数;若全为负数,则缺失的是 n+1
代码实现
class Solution {public int firstMissingPositive(int[] nums) {int n = nums.length;// 步骤1:将所有非正数替换为 n+1for (int i = 0; i < n; i++) {if (nums[i] <= 0) {nums[i] = n + 1;}}// 步骤2:标记出现过的数for (int i = 0; i < n; i++) {int num = Math.abs(nums[i]); // 取绝对值(可能已被标记为负)if (num <= n) {// 将 num-1 位置的值置为负数(表示 num 出现过)nums[num - 1] = -Math.abs(nums[num - 1]); }}// 步骤3:查找第一个正数位置for (int i = 0; i < n; i++) {if (nums[i] > 0) {return i + 1; // 当前位置未标记,i+1 即为缺失的数}}return n + 1; // 所有位置均被标记,缺失 n+1}
}

关键步骤解析
  1. 预处理非正数
   for (int i = 0; i < n; i++) {if (nums[i] <= 0) {nums[i] = n + 1; // 替换为超出范围的值}}

  • 非正数不影响结果,替换为 n+1 后,数组中只剩正数,便于后续标记。
  1. 负数标记法
   int num = Math.abs(nums[i]); // 获取原始值(可能已被其他操作标记为负)if (num <= n) {nums[num - 1] = -Math.abs(nums[num - 1]); // 确保标记为负数}

  • 绝对值的作用:若 nums[i] 已被标记为负,Math.abs 能还原其原始值。
  • 负号标记:通过将 nums[num-1] 设为负数,表示 num 出现过(如 num=3 则标记索引 2)。
  • 避免重复标记Math.abs(...) 确保即使位置已被标记,也不会因二次取负变正。
  1. 查找缺失数
   for (int i = 0; i < n; i++) {if (nums[i] > 0) {return i + 1; // 位置 i 未标记,说明 i+1 缺失}}return n + 1;

  • 若位置 i 的值为正,说明 i+1 未出现过(未被标记)。
复杂度分析
  • 时间复杂度:三次遍历,总计 O(n)。
  • 空间复杂度:O(1),仅用常量空间。
示例推演

nums = [3, 4, -1, 1] 为例:

  1. 预处理:替换非正数 -1 → 5,数组变为 [3, 4, 5, 1]
  2. 标记出现过的数
    • 3 → 标记索引 2[3, 4, -5, 1]
    • 4 → 标记索引 3[3, 4, -5, -1]
    • 5 → 跳过(超出范围)
    • 1 → 标记索引 0[-3, 4, -5, -1]
  3. 查找缺失数:索引 1 的值为正数 4 → 缺失数 1+1=2
总结

通过原地利用数组下标进行标记,避免了额外空间开销。核心技巧是:

  1. 预处理排除干扰值。
  2. 用负号标记出现过的数(注意重复标记问题)。
  3. 通过正数位置定位缺失值。

此方法简洁高效,充分体现了数组下标与值之间的映射关系,是解决此类问题的经典思路。

http://www.xdnf.cn/news/1438327.html

相关文章:

  • Qwen3-30B-A3B 模型解析
  • 【C++】迭代器详解与失效机制
  • # Shell 文本处理三剑客:awk、sed 与常用小工具详解
  • 【前端面试题✨】Vue篇(一)
  • Linux网络序列化与反序列化(6)
  • Linux文本处理——awk
  • 飞牛OS Nas,SSH安装宝塔后,smb文件不能共享问题
  • STM32——串口
  • 2025年- H109-Lc1493. 删掉一个元素以后全为 1 的最长子数组(双指针)--Java版
  • 别再误会了!Redis 6.0 的多线程,和你想象的完全不一样
  • 从入门到实战:Linux sed命令全攻略,文本处理效率翻倍
  • 【机器学习深度学习】向量模型与重排序模型:RAG 的双引擎解析
  • 使用DataLoader加载本地数据 食物分类案例
  • GitHub Classroom:编程教育的高效协作方案
  • MySQL查询limit 0,100和limit 10000000,100有什么区别?
  • Shell编程从入门到实践:基础语法与正则表达式文本处理指南
  • 如何在部署模型前训练出完美的AI提示词
  • C# 中这几个主流的 ORM(对象关系映射器):Dapper、Entity Framework (EF) Core 和 EF 6
  • 11.《简单的路由重分布基础知识探秘》
  • 硬件:51单片机
  • 为什么需要锁——多线程的数据竞争是怎么引发错误的
  • 系统架构——过度设计
  • YOLOv8改进有效系列大全:从卷积到检测头的百种创新机制解析
  • 【C++上岸】C++常见面试题目--数据结构篇(第十七期)
  • 02-Media-2-ai_rtsp.py 人脸识别加网络画面RTSP推流演示
  • 51单片机(单片机基础,LED,数码管)
  • Spring Boot手写10万敏感词检查程序
  • UCIE Specification详解(十三)
  • C++ 条件变量,互斥锁
  • 【c++】多态+RTTI (运行时的类型识别信息)