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的来源,一次交换之后,当前位置会出现一个新元素;只要这个新元素还能被有效安置,就继续处理当前位置。

相关推荐
专注仿真3 小时前
问答大模型技术方案算法实现-熵权法融合算法 + 交叉编码器重排算法
人工智能·python·算法·语言模型·问答大模型关键算法·熵权融合算法·交叉编码器重排算法
地平线开发者4 小时前
量化为什么会有损失:从量化误差到精度下降
算法
手写码匠5 小时前
华为云Flexus+DeepSeek征文|Dify 多 Agent 会话管理与上下文工程实战:让智能体“记住该记住的,忘掉该忘掉的“
人工智能·深度学习·算法·aigc
2401_862880825 小时前
数据结构 --- 栈
c语言·数据结构·算法
致Great5 小时前
Qwen3.8-27B 接入 DSH 全流程实测:写代码、跑长任务,都没问题
算法
Tisfy6 小时前
LeetCode 3471.找出最大的几近缺失整数:三种情况判断
算法·leetcode·题解·分类讨论
linux-hzh6 小时前
百日算法修炼 · Day 12
算法·深度优先
额额额对了6 小时前
数据结构:二叉树
c语言·数据结构·算法
iNeuOS工业互联网7 小时前
iNeuOS_Vision_视觉分析,增加SAM3模型对实例分割和目标检测AI自动标注图片样本功能
人工智能·算法·目标检测