LeetCode题目41:缺失的第一个正数

简介: LeetCode题目41:缺失的第一个正数

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

LeetCode题目41“缺失的第一个正数”要求在未排序的整数数组中找出最小的未出现的正整数。本文将详细介绍三种解决这一问题的方法,包括解题思路、详细的代码实现以及算法分析。

题目描述

输入:一个未排序的整数数组 nums

输出:未在数组中出现的最小正整数。

示例

输入: [1,2,0]
输出: 3
输入: [3,4,-1,1]
输出: 2
输入: [7,8,9,11,12]
输出: 1

方法一:索引作为哈希键

解题步骤

  1. 处理负数和零:由于我们只关心正数,可以先将所有非正整数替换为一个大于数组长度的值(例如 n+1,其中 n 是数组长度)。
  2. 利用索引标记:遍历数组,使用数组索引作为哈希键来标记存在的数字。具体操作是,对于每个数字 x,如果 1 <= x <= n,则将索引 x-1 的位置的数字设置为负数。
  3. 寻找结果:再次遍历数组,第一个索引位置 i 使得 nums[i] > 0i + 1 就是缺失的最小正数。

代码示例

def firstMissingPositive(nums):
    n = len(nums)
    # 将所有的负数和零替换为n+1,n+1是一个不可能出现在合法输出中的数字
    for i in range(n):
        if nums[i] <= 0:
            nums[i] = n + 1
            
    # 使用数组索引作为哈希键,通过置负标记存在的数字
    for i in range(n):
        num = abs(nums[i])
        if num <= n:
            nums[num - 1] = -abs(nums[num - 1])
    
    # 寻找第一个大于0的索引位置,即是缺失的最小正数
    for i in range(n):
        if nums[i]> 0:
            return i + 1
    # 如果数组中包含了1到n的所有数字,则缺失的第一个正数是n+1
    return n + 1
# 示例调用
print(firstMissingPositive([1, 2, 0]))  # 输出: 3
print(firstMissingPositive([3, 4, -1, 1]))  # 输出: 2
print(firstMissingPositive([7, 8, 9, 11, 12]))  # 输出: 1

算法分析

  • 时间复杂度:O(N),其中 N 是数组长度。算法涉及几个线性扫描,每个扫描的时间复杂度都是O(N)。
  • 空间复杂度:O(1)。除了输入数组外,没有使用额外的空间,所有操作均在原数组上进行。

方法二:排序后线性扫描

解题步骤

  1. 排序数组:首先对数组进行排序。
  2. 线性扫描:遍历排序后的数组,查找第一个缺失的最小正数。

代码示例

def firstMissingPositive(nums):
    nums.sort()
    target = 1
    for num in nums:
        if num == target:
            target += 1
    return target
# 示例调用
print(firstMissingPositive([1, 2, 0]))  # 输出: 3
print(firstMissingPositive([3, 4, -1, 1]))  # 输出: 2
print(firstMissingPositive([7, 8, 9, 11, 12]))  # 输出: 1

算法分析

  • 时间复杂度:O(N log N),主要时间开销来源于排序。
  • 空间复杂度:O(1) 或 O(N),这取决于使用的排序算法。

方法三:使用哈希表

解题步骤

  1. 使用哈希表存储:将所有正数存储在哈希表中。
  2. 寻找最小正数:从 1 开始递增寻找不在哈希表中的最小正数。

代码示例

def firstMissingPositive(nums):
    num_set = set(nums)
    target = 1
    while target in num_set:
        target += 1
    return target
# 示例调用
print(firstMissingPositive([1, 2, 0]))  # 输出: 3
print(firstMissingPositive([3, 4, -1, 1]))  # 输出: 2
print(firstMissingPositive([7, 8, 9, 11, 12]))  # 输出: 1

算法分析

  • 时间复杂度:O(N),虽然使用了哈希表,但构建哈希表和查询的总时间仍是线性的。
  • 空间复杂度:O(N),需要额外的空间来存储哈希表。

总结

为了直观地比较解决LeetCode题目41 "缺失的第一个正数"的三种方法,下面的表格将展示它们在不同技术维度的性能和特点,包括时间复杂度、空间复杂度以及各自的优势和劣势。

特征 方法一:索引作为哈希键 方法二:排序后线性扫描 方法三:使用哈希表
时间复杂度 O(N) O(N log N) O(N)
空间复杂度 O(1) O(1) 或 O(N) O(N)
优势 - 不使用额外空间
- 快速且高效
- 直接在原数组上操作,不需要额外的数据结构
- 实现简单
- 直观易理解
- 适用于不限制时间复杂度的场景
- 实现简单
- 查找速度快
- 直接使用Python内置数据结构
劣势 - 算法理解相对复杂
- 需要修改原数组作为标记
- 时间复杂度较高,尤其是在数据量大时
- 空间复杂度依赖于排序算法
- 空间开销大
- 需要额外空间存储所有正数
适用场景 - 数据量大且对空间有严格限制时
- 需要在原地解决问题时
- 数据预处理和排序成本低时
- 对算法执行时间要求不严格时
- 空间充足时
- 需要快速实现和简洁代码时


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
1月前
|
算法
LeetCode第12题目整数转罗马数字
该文章介绍了 LeetCode 第 12 题整数转罗马数字的解法,通过使用 TreeMap 按照整数从大到小排序,先使用大的罗马数字表示整数,再用小的,核心是先表示完大的罗马数字,想通此点该题较简单。
LeetCode第12题目整数转罗马数字
|
1月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
38 6
|
1月前
|
算法
LeetCode第13题目罗马数字转整数
该文章介绍了 LeetCode 第 13 题罗马数字转整数的解法,通过从大到小解析罗马数字,根据罗马数字的特点,按照从大到小的顺序匹配罗马数字和整数的关系,从而解决该问题,同时强调要注意观察题目考查的知识点特征。
|
3月前
|
C语言
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
25 1
|
2月前
|
存储 算法
经典的滑动窗口的题目 力扣 2799. 统计完全子数组的数目(面试题)
经典的滑动窗口的题目 力扣 2799. 统计完全子数组的数目(面试题)
|
3月前
|
算法
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
|
3月前
|
容器
【LeetCode刷题】栈和队列题目练习~
【LeetCode刷题】栈和队列题目练习~
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(4)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
3月前
|
存储 算法
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(1)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
3月前
|
算法
【经典LeetCode算法题目专栏分类】【第11期】递归问题:字母大小写全排列、括号生成
【经典LeetCode算法题目专栏分类】【第11期】递归问题:字母大小写全排列、括号生成