LeetCode hot 100 —41. 缺失的第一个正数

题目信息

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

示例 1:

复制代码
输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。

示例 2:

复制代码
输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。

示例 3:

复制代码
输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。

提示:

  • 1 <= nums.length <= 10^5
  • -2^31 <= nums[i] <= 2^31 - 1

思考与自写代码

依旧是能写且可以通过题目考察,但不符合要求

这题的难点在于空间复杂度,我写的代码没有满足常数级别的空间要求。自写代码如下:

python 复制代码
class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        # 最小的正整数肯定是1
        # 要求时间复杂度为O(N),所以这里不能进行排序. 排序的时复杂度O(Nlog N)高于(也就是慢于)O(N)
        ans = 1
        seen = set()

        for num in nums: seen.add(num)

        flag = True
        while flag:
            if ans in seen: ans += 1
            else: flag = False
        return ans

标准代码

先要想明白一个事情 / 发现一个边界:长度为 n 的数组,缺失的最小正整数一定在 [1, n + 1] 中。 如果 1~n 中存在缺失值,答案就在其中;如果全部存在,答案就是 n + 1。所以小于等于 0 或大于 n 的数字都可以忽略。

为了不用 set,可以把原数组本身当作哈希表:

复制代码
数字 1 应该放在下标 0
数字 2 应该放在下标 1
数字 3 应该放在下标 2
......
数字 x 应该放在下标 x - 1

代码如下:

python 复制代码
class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        n = len(nums) # 必定在[1, n + 1]范围内

        for i in range(n):
            # 大循环是下标遍历,确保每个位置的数字被"合理"放置. nums[x - 1] = x (nums[0] = 1, nums[1] = 1, ...)
            while(
                1 <= nums[i] <= n    # 在答案区间范围内
                and nums[nums[i] - 1] != nums[i] # 位置不合规
            ):
                target_index = nums[i] - 1
                nums[i], nums[target_index] = nums[target_index], nums[i] # 交换两个位置的数字
        
        for i in range(n):
            # 所有的x都被放到了index为x - 1的位置,所以遍历过去,如果nums[x - 1] != x,证明x没有出现过
            if nums[i] != i + 1:
                return i + 1
        return n + 1 # 否则相当于一次循环排序,也即nums本身是[1, 2, 3, ..., n]的一个乱序排列

代码分析 / 解析

看了代码以后我的第一反应是:逻辑我可以理解,但是我自己撰写的时候肯定会被这个while和交换给困住,我不知道如何不重复、不遗漏的,将每个数字放在我认为的"正确"的位置上

所以思维过程应该是,首先不要想完整代码应该如何写,先定义好目标状态是什么:

先不要想完整代码,只定义目标状态:

复制代码
如果数字 x 在 [1, n] 中,就尽量把它放到下标 x - 1。

例如数字 3 的目标位置是:

复制代码
nums[2]
为什么需要交换

为了确保数字不会被覆盖 / 丢失。

当开始遍历,我会将遇到的数字 x(当前的处理位) 放到 index 为 x - 1的位置,如果我直接覆盖,那么原有的numsx - 1会丢失。

为了不引入额外的空间去存放numsx - 1,这里进行交换。那么此时 x 去到了正确位置,但当前处理位置变成了一个新的数字 numsx - 1

为什么是 while 不是 if

while循环确保对当前位置的处理是彻底的。当前位置被处理得当,才会进行下标的移动。

如果只使用一次 if,处理完 x 并且完成交换以后就会进入下一个下标,但当前位置的 numsx - 1 仍然没有被处理,或者说不能确定 一定被放到了正确的位置。

所以这就是while的来源,一次交换之后,当前位置会出现一个新元素;只要这个新元素还能被有效安置,就继续处理当前位置。

相关推荐
政企项目老覃9 小时前
大模型幻觉治理与自动评测:金融风控场景的落地实践
人工智能·算法·机器学习
淡海水9 小时前
08-03-不可变-ImmutableDictionary-TKey-TValue-与ImmutableHashSet-T-持久化哈希树
数据结构·算法·c#·哈希算法·dictionary·immutable
hansang_IR9 小时前
【题解】[APIO2023] 赛博乐园 / cyberland
c++·算法·图论
洛阳纸贵10 小时前
MATLAB-matlab基础知识
学习·算法·matlab
落羽的落羽10 小时前
【AI】快速理解AI应用的相关名词概念
linux·c++·人工智能·python·计算机网络·算法
Nil20810 小时前
leetcode 17电话号码的字母组合
算法·leetcode·职场和发展
203号居民12 小时前
LeetCode hot 100 — 25. K 个一组翻转链表
算法·leetcode·链表
ocean210313 小时前
2025-2026年AI算法与模型研发面试高频知识点洞察
人工智能·算法·面试
Nil20813 小时前
leetcode 78子集
数据结构·算法·leetcode