力扣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
代码实现
java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;

        // 步骤1:将所有非正数替换为 n+1
        for (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. 预处理非正数
java 复制代码
   for (int i = 0; i < n; i++) {
       if (nums[i] <= 0) {
           nums[i] = n + 1; // 替换为超出范围的值
       }
   }
  • 非正数不影响结果,替换为 n+1 后,数组中只剩正数,便于后续标记。
  1. 负数标记法
java 复制代码
   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. 查找缺失数
java 复制代码
   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. 预处理 :替换非正数 -15,数组变为 [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. 通过正数位置定位缺失值。

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

相关推荐
民乐团扒谱机7 分钟前
【微实验】组合优化matlab实战(马科维茨投资模型):在收益与风险之间,寻找最优的人生配比
大数据·人工智能·算法·机器学习·数学建模·matlab·组合优化
名字还没想好☜8 分钟前
Java 线上内存泄漏排查实战:jmap 导堆、MAT 找 GC Roots 与四类常见泄漏
java·开发语言·jvm·内存泄漏
Nil2081 小时前
leetcode 160相交链表
算法·leetcode·链表
IT_陈寒1 小时前
Vue的双向绑定把我坑惨了,原来这个场景不能用
前端·人工智能·后端
迷途之人不知返2 小时前
算法系列2:滑动窗口
算法
IT爱学堂2 小时前
尚硅谷 - 2025年3月Java+AI大模型应用开发
java·开发语言·人工智能
Herbert_hwt2 小时前
C语言零基础入门:循环控制与数据类型详解
c语言·数据结构·算法
小贤plus3 小时前
SpringBoot 三大核心注解精讲:@ControllerAdvice、@RestControllerAdvice、@Validated 分组校验(实战)
java
31535669133 小时前
DeepSeek Harness 发布后,我没急着跑 Demo,先把 `.agents/` 翻了一遍
前端·后端·github
Nturmoils3 小时前
不在公司,也能连回办公电脑:用 Natapp 打通 Windows 远程桌面
后端