力扣41.缺失的第一个正数:一场数字的"归位"游戏

23 阅读4分钟

image.png

题目回顾

给定一个未排序的整数数组 nums,找出没有出现的最小正整数。要求:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)(只能使用常数级别的额外空间)

示例:

  • [1,2,0] → 3(1和2都在,下一个就是3)
  • [3,4,-1,1] → 2(1在,但2缺失)
  • [7,8,9,11,12] → 1(1就直接缺失)

核心思想:把数组当成"房子"

这道题最巧妙的地方在于:我们直接把原数组当成哈希表来用,不额外占空间。

关键观察:

  • 数组长度是 n,那么答案一定在 [1, n+1] 这个范围内
    • 最小的正整数最大不会超过 n+1
    • 如果 1~n 都在,答案就是 n+1

核心策略:让每个数字"回到自己的家"

想象数组的每个位置是一栋房子,门牌号从 0n-1。数字 i 的"家"应该在门牌号 i-1 的位置(因为数组是0索引)。

游戏规则: 只有数字 i 在范围 [1, n] 内,它才有资格住进自己的家。


详细步骤(三步走)

第一步:忽略"不合格"的数字

遍历数组,我们只关心 1~n 范围内的数字。负数、0、大于n的数都是"黑户",暂时不管它们。

第二步:让数字"各回各家"

再次遍历,对于每个数字 nums[i]

  • 如果 1 ≤ nums[i] ≤ n,它就应该住在索引 nums[i]-1 的位置
  • 一直交换,直到:
    • 数字已经在正确位置了(nums[i] == i+1
    • 或者要交换的位置已经是对的数字了(避免死循环)

这个过程就像:

你走进一个乱糟糟的走廊,看到门牌3号房门口站着数字5,你就说:"喂,5,你的家在4号房!" 然后你把5送到4号房门口,结果4号房门口是数字2,你继续送2回家...直到所有人都站对位置为止。

第三步:找出第一个"空房子"

再遍历一次数组,找到第一个数值不等于索引+1的位置:

  • nums[i] ≠ i+1 → 答案就是 i+1

如果所有位置都对,答案就是 n+1


代码实现(Java)

public class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        
        // 第二步:让数字"各回各家"
        for (int i = 0; i < n; i++) {
            // 当nums[i]在[1,n]范围内且不在正确位置时,持续交换
            while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                // 交换nums[i]和nums[nums[i] - 1]
                int temp = nums[nums[i] - 1];
                nums[nums[i] - 1] = nums[i];
                nums[i] = temp;
            }
        }
        
        // 第三步:找出第一个"空房子"
        for (int i = 0; i < n; i++) {
            if (nums[i] != i + 1) {
                return i + 1;  // 这个位置该住i+1,但没住对
            }
        }
        
        // 都住对了,答案就是n+1
        return n + 1;
    }
}

示例演示:nums = [3,4,-1,1]

数组长度 n = 4,我们关注数字 1~4

初始状态:

索引:  0  1   2  3
数值: [3, 4, -1, 1]

第二步过程:

  • i=0: nums[0]=3,应该在索引2 → 交换位置0和2 索引: 0 1 2 3 数值: [-1, 4, 3, 1] nums[0]现在=-1(不合格),继续下一个i

  • i=1: nums[1]=4,应该在索引3 → 交换位置1和3 索引: 0 1 2 3 数值: [-1, 1, 3, 4]

  • i=1(继续): nums[1]=1,应该在索引0 → 交换位置1和0 索引: 0 1 2 3 数值: [1, -1, 3, 4]

  • i=2: nums[2]=3,已在正确位置(索引2)

  • i=3: nums[3]=4,已在正确位置(索引3)

最终状态:

索引:  0   1  2  3
数值: [1, -1, 3, 4]

第三步检查:

  • 索引0: nums[0]=1 ✓
  • 索引1: nums[1]=-1 ≠ 2答案是 2

复杂度分析

  • 时间复杂度 O(n)

    • 每次交换至少把一个数字放到正确位置
    • 最多进行 n 次交换(每次交换固定一个数字)
    • 所以总时间是线性的
  • 空间复杂度 O(1)

    • 只使用了几个临时变量
    • 没有使用额外的数组或数据结构

为什么这个方法有效?

关键性质: 当我们完成第二步后,对于所有在 [1,n] 范围内的数,如果它存在,它一定在正确的位置上

换句话说:如果数字 k(1≤k≤n)存在于数组中,那么 nums[k-1] 一定等于 k。

因此,当我们发现 nums[i] ≠ i+1 时,就意味着数字 i+1 根本不存在于数组中!


总结

这道题的精髓在于原地哈希

  1. 空间换时间 → 用数组本身当哈希表
  2. 数字归位 → 每个数字都有"命中注定"的位置
  3. 一次扫描 → 线性时间找到答案

就像整理书架:每本书都有固定编号的位置,我们只要把书放回正确位置,然后扫一眼就能知道哪本缺失了。

希望这篇文章能帮助你理解这道经典题目!