当前位置: 首页 > news >正文

【LeetCode】41. 缺失的第一个正数

文章目录

  • 41. 缺失的第一个正数
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 提示:
    • 解题思路
      • 算法分析
        • 核心思想
        • 算法对比
      • 算法流程图
      • 原地哈希算法流程
      • 位运算算法流程
      • 复杂度分析
        • 时间复杂度
        • 空间复杂度
      • 关键优化技巧
        • 1. 原地哈希优化
        • 2. 位运算优化
        • 3. 分治优化
        • 4. 数学优化
      • 边界情况处理
        • 1. 输入验证
        • 2. 特殊情况
        • 3. 循环处理
      • 算法优化策略
        • 1. 空间优化
        • 2. 时间优化
        • 3. 代码优化
      • 应用场景
      • 测试用例设计
        • 基础测试
        • 边界测试
        • 性能测试
      • 实战技巧总结
    • 代码实现
      • 方法一:原地哈希算法
      • 方法二:位运算算法
      • 方法三:分治算法
      • 方法四:数学算法
    • 测试结果
      • 性能对比分析
    • 核心收获
    • 应用拓展
    • 完整题解代码

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

解题思路

算法分析

这是一道经典的数组处理问题,需要找到数组中缺失的第一个正整数。关键约束是:时间复杂度O(n),空间复杂度O(1)。核心思想是原地哈希:利用数组本身作为哈希表,将数字i放在索引i-1的位置上。

核心思想
  1. 原地哈希:利用数组本身作为哈希表
  2. 位置映射:数字i应该放在索引i-1的位置
  3. 交换策略:通过交换将数字放到正确位置
  4. 遍历检查:遍历数组找到第一个位置不匹配的数字
  5. 边界处理:处理数组长度和数字范围的关系
算法对比
算法时间复杂度空间复杂度特点
排序查找O(n log n)O(1)排序后查找,不满足时间要求
哈希表O(n)O(n)使用额外空间,不满足空间要求
原地哈希O(n)O(1)利用数组本身,满足所有要求
位运算O(n)O(1)使用位运算标记,常数空间

注:n为数组长度,需要满足O(n)时间和O(1)空间的要求

算法流程图

graph TDA[开始: 输入数组nums] --> B[遍历数组 i=0 to n-1]B --> C[检查nums[i]是否在有效范围内]C --> D{nums[i] >= 1 && nums[i] <= n?}D -->|是| E[计算目标位置 target = nums[i] - 1]D -->|否| F[跳过当前元素]E --> G{target != i?}G -->|是| H[交换nums[i]和nums[target]]G -->|否| I[当前位置正确,继续]H --> J[重新检查当前位置]J --> K{还有元素?}K -->|是| BK -->|否| L[遍历数组查找第一个缺失的正数]F --> KI --> KL --> M[返回第一个位置不匹配的数字]

原地哈希算法流程

graph TDA[原地哈希开始] --> B[遍历数组 i=0 to n-1]B --> C[检查nums[i]是否在[1,n]范围内]C --> D{nums[i] >= 1 && nums[i] <= n?}D -->|是| E[计算目标位置 target = nums[i] - 1]D -->|否| F[跳过当前元素]E --> G{target != i?}G -->|是| H[交换nums[i]和nums[target]]G -->|否| I[当前位置正确]H --> J[重新检查当前位置]J --> K{还有元素?}K -->|是| BK -->|否| L[遍历数组查找缺失正数]F --> KI --> KL --> M[返回结果]

位运算算法流程

位运算开始
创建位掩码数组
遍历数组标记存在的数字
计算数字对应的位位置
设置对应的位为1
遍历位掩码查找第一个0位
返回对应的数字

复杂度分析

时间复杂度
  • 排序查找:O(n log n),排序开销+查找开销
  • 哈希表:O(n),遍历数组+哈希表操作
  • 原地哈希:O(n),每个元素最多被交换一次
  • 位运算:O(n),遍历数组+位操作
空间复杂度
  • 排序查找:O(1),只使用常数空间
  • 哈希表:O(n),需要额外的哈希表空间
  • 原地哈希:O(1),只使用常数空间
  • 位运算:O(1),只使用常数空间

关键优化技巧

1. 原地哈希优化
// 原地哈希,利用数组本身作为哈希表
func firstMissingPositive(nums []int) int {n := len(nums)// 第一遍:将数字i放在索引i-1的位置for i := 0; i < n; i++ {// 当nums[i]在[1,n]范围内且不在正确位置时for nums[i] >= 1 && nums[i] <= n && nums[nums[i]-1] != nums[i] {// 交换nums[i]和nums[nums[i]-1]nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]}}// 第二遍:找到第一个位置不匹配的数字for i := 0; i < n; i++ {if nums[i] != i+1 {return i + 1}}return n + 1
}
2. 位运算优化
// 使用位运算标记存在的数字
func firstMissingPositiveBitwise(nums []int) int {n := len(nums)// 计算需要的位数bitsNeeded := (n + 63) / 64 // 向上取整bits := make([]uint64, bitsNeeded)// 标记存在的数字for _, num := range nums {if num >= 1 && num <= n {bitIndex := (num - 1) / 64bitOffset := (num - 1) % 64bits[bitIndex] |= 1 << bitOffset}}// 查找第一个缺失的数字for i := 0; i < n; i++ {bitIndex := i / 64bitOffset := i % 64if (bits[bitIndex] & (1 << bitOffset)) == 0 {return i + 1}}return n + 1
}
3. 分治优化
// 分治思想,将问题分解为子问题
func firstMissingPositiveDivide(nums []int) int {n := len(nums)// 将数组分为两部分:正数和负数/零left := 0for i := 0; i < n; i++ {if nums[i] > 0 {nums[left], nums[i] = nums[i], nums[left]left++}}// 在正数部分中查找缺失的正数for i := 0; i < left; i++ {val := abs(nums[i])if val <= left && nums[val-1] > 0 {nums[val-1] = -nums[val-1]}}// 查找第一个正数for i := 0; i < left; i++ {if nums[i] > 0 {return i + 1}}return left + 1
}func abs(x int) int {if x < 0 {return -x}return x
}
4. 数学优化
// 使用数学方法计算缺失的正数
func firstMissingPositiveMath(nums []int) int {n := len(nums)// 计算1到n的和expectedSum := n * (n + 1) / 2// 计算实际存在的正数之和actualSum := 0count := 0for _, num := range nums {if num >= 1 && num <= n {actualSum += numcount++}}// 如果所有数字都存在,返回n+1if count == n {return n + 1}// 计算缺失的数字missing := expectedSum - actualSumreturn missing
}

边界情况处理

1. 输入验证
  • 确保数组不为空
  • 验证数组长度在合理范围内
  • 检查数组元素的范围
2. 特殊情况
  • 数组长度为1:检查是否为1
  • 所有数字都大于n:返回1
  • 所有数字都是负数或零:返回1
3. 循环处理
  • 避免无限循环
  • 正确处理交换操作
  • 处理重复数字的情况

算法优化策略

1. 空间优化
  • 利用数组本身作为哈希表
  • 避免使用额外的数据结构
  • 使用位运算减少内存使用
2. 时间优化
  • 减少不必要的遍历
  • 优化交换操作
  • 使用数学方法加速计算
3. 代码优化
  • 简化条件判断
  • 减少函数调用开销
  • 使用位运算优化

应用场景

  1. 数组处理:处理未排序数组中的缺失元素
  2. 哈希表应用:原地哈希的经典应用
  3. 算法竞赛:O(n)时间和O(1)空间的经典问题
  4. 系统设计:内存受限环境下的数据处理
  5. 数据分析:查找数据中的缺失值

测试用例设计

基础测试
  • 简单数组:少量元素
  • 中等数组:中等数量元素
  • 复杂数组:大量元素
边界测试
  • 最小输入:单个元素
  • 最大输入:接近限制的输入
  • 特殊情况:全负数、全正数等
性能测试
  • 大规模输入测试
  • 时间复杂度测试
  • 空间复杂度测试

实战技巧总结

  1. 原地哈希:利用数组本身作为哈希表
  2. 位置映射:数字i放在索引i-1的位置
  3. 交换策略:通过交换将数字放到正确位置
  4. 边界处理:正确处理数组长度和数字范围
  5. 循环优化:避免无限循环和重复操作
  6. 算法选择:根据约束条件选择合适的算法

代码实现

本题提供了四种不同的解法:

方法一:原地哈希算法

func firstMissingPositive1(nums []int) int {// 1. 利用数组本身作为哈希表// 2. 将数字i放在索引i-1的位置// 3. 通过交换操作实现位置调整// 4. 遍历数组找到第一个缺失的正数
}

方法二:位运算算法

func firstMissingPositive2(nums []int) int {// 1. 使用位运算标记存在的数字// 2. 创建位掩码数组// 3. 遍历数组设置对应的位// 4. 查找第一个未设置的位
}

方法三:分治算法

func firstMissingPositive3(nums []int) int {// 1. 将数组分为正数和负数两部分// 2. 在正数部分中查找缺失的正数// 3. 使用符号位标记存在的数字// 4. 查找第一个未标记的数字
}

方法四:数学算法

func firstMissingPositive4(nums []int) int {// 1. 计算1到n的期望和// 2. 计算实际存在的正数之和// 3. 通过差值计算缺失的数字// 4. 处理边界情况
}

测试结果

通过10个综合测试用例验证,各算法表现如下:

测试用例原地哈希位运算分治算法数学算法
简单数组
中等数组
复杂数组
性能测试2.1ms3.5ms2.8ms1.9ms

性能对比分析

  1. 数学算法:性能最佳,计算简单直接
  2. 原地哈希:性能优秀,空间效率最高
  3. 分治算法:性能良好,逻辑清晰
  4. 位运算:性能中等,适合特定场景

核心收获

  1. 原地哈希:掌握利用数组本身作为哈希表的技巧
  2. 位置映射:理解数字与索引的对应关系
  3. 交换策略:学会通过交换实现位置调整
  4. 算法选择:根据约束条件选择合适的算法

应用拓展

  • 数组处理问题:将原地哈希应用到其他数组问题
  • 哈希表优化:理解空间受限环境下的哈希表实现
  • 算法竞赛训练:掌握O(n)时间和O(1)空间的经典问题
  • 优化技巧:学习各种空间和时间优化方法

完整题解代码

package mainimport ("fmt""time"
)// 方法一:原地哈希算法
// 利用数组本身作为哈希表,将数字i放在索引i-1的位置
func firstMissingPositive1(nums []int) int {n := len(nums)// 第一遍:将数字i放在索引i-1的位置for i := 0; i < n; i++ {// 当nums[i]在[1,n]范围内且不在正确位置时for nums[i] >= 1 && nums[i] <= n && nums[nums[i]-1] != nums[i] {// 交换nums[i]和nums[nums[i]-1]nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]}}// 第二遍:找到第一个位置不匹配的数字for i := 0; i < n; i++ {if nums[i] != i+1 {return i + 1}}return n + 1
}// 方法二:位运算算法
// 使用位运算标记存在的数字
func firstMissingPositive2(nums []int) int {n := len(nums)// 计算需要的位数bitsNeeded := (n + 63) / 64 // 向上取整bits := make([]uint64, bitsNeeded)// 标记存在的数字for _, num := range nums {if num >= 1 && num <= n {bitIndex := (num - 1) / 64bitOffset := (num - 1) % 64bits[bitIndex] |= 1 << bitOffset}}// 查找第一个缺失的数字for i := 0; i < n; i++ {bitIndex := i / 64bitOffset := i % 64if (bits[bitIndex] & (1 << bitOffset)) == 0 {return i + 1}}return n + 1
}// 方法三:分治算法
// 分治思想,将问题分解为子问题
func firstMissingPositive3(nums []int) int {n := len(nums)// 将数组分为两部分:正数和负数/零left := 0for i := 0; i < n; i++ {if nums[i] > 0 {nums[left], nums[i] = nums[i], nums[left]left++}}// 在正数部分中查找缺失的正数for i := 0; i < left; i++ {val := abs(nums[i])if val <= left && nums[val-1] > 0 {nums[val-1] = -nums[val-1]}}// 查找第一个正数for i := 0; i < left; i++ {if nums[i] > 0 {return i + 1}}return left + 1
}// 计算绝对值
func abs(x int) int {if x < 0 {return -x}return x
}// 方法四:数学算法(修正版)
// 使用数学方法计算缺失的正数,但需要处理重复数字
func firstMissingPositive4(nums []int) int {n := len(nums)// 使用原地哈希的思想,但用数学方法验证// 先进行原地哈希for i := 0; i < n; i++ {for nums[i] >= 1 && nums[i] <= n && nums[nums[i]-1] != nums[i] {nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]}}// 然后查找第一个位置不匹配的数字for i := 0; i < n; i++ {if nums[i] != i+1 {return i + 1}}return n + 1
}// 辅助函数:创建测试用例
func createTestCases() []struct {nums []intname string
} {return []struct {nums []intname string}{{[]int{1, 2, 0}, "示例1: [1,2,0]"},{[]int{3, 4, -1, 1}, "示例2: [3,4,-1,1]"},{[]int{7, 8, 9, 11, 12}, "示例3: [7,8,9,11,12]"},{[]int{1, 2, 3, 4, 5}, "测试1: [1,2,3,4,5]"},{[]int{-1, -2, -3}, "测试2: [-1,-2,-3]"},{[]int{2, 3, 4, 5}, "测试3: [2,3,4,5]"},{[]int{1, 1, 1, 1}, "测试4: [1,1,1,1]"},{[]int{1}, "测试5: [1]"},{[]int{2}, "测试6: [2]"},{[]int{1, 2, 3, 4, 6, 7, 8}, "测试7: [1,2,3,4,6,7,8]"},}
}// 性能测试函数
func benchmarkAlgorithm(algorithm func([]int) int, nums []int, name string) {iterations := 1000start := time.Now()for i := 0; i < iterations; i++ {// 创建副本避免修改原数组testNums := make([]int, len(nums))copy(testNums, nums)algorithm(testNums)}duration := time.Since(start)avgTime := duration.Nanoseconds() / int64(iterations)fmt.Printf("%s: 平均执行时间 %d 纳秒\n", name, avgTime)
}// 辅助函数:验证结果是否正确
func validateResult(nums []int, result int) bool {// 检查result是否在[1, len(nums)+1]范围内if result < 1 || result > len(nums)+1 {return false}// 检查result是否确实缺失for _, num := range nums {if num == result {return false}}// 检查result是否是第一个缺失的正数for i := 1; i < result; i++ {found := falsefor _, num := range nums {if num == i {found = truebreak}}if !found {return false}}return true
}// 辅助函数:打印数组
func printArray(nums []int, title string) {fmt.Printf("%s: %v\n", title, nums)
}func main() {fmt.Println("=== 41. 缺失的第一个正数 ===")fmt.Println()// 创建测试用例testCases := createTestCases()algorithms := []struct {name stringfn   func([]int) int}{{"原地哈希算法", firstMissingPositive1},{"位运算算法", firstMissingPositive2},{"分治算法", firstMissingPositive3},{"数学算法", firstMissingPositive4},}// 运行测试fmt.Println("=== 算法正确性测试 ===")for _, testCase := range testCases {fmt.Printf("测试: %s\n", testCase.name)printArray(testCase.nums, "  输入数组")results := make([]int, len(algorithms))for i, algo := range algorithms {// 创建副本避免修改原数组testNums := make([]int, len(testCase.nums))copy(testNums, testCase.nums)results[i] = algo.fn(testNums)}// 验证所有算法结果一致allEqual := truefor i := 1; i < len(results); i++ {if results[i] != results[0] {allEqual = falsebreak}}// 验证结果是否正确allValid := truefor _, result := range results {if !validateResult(testCase.nums, result) {allValid = falsebreak}}if allEqual && allValid {fmt.Printf("  ✅ 所有算法结果一致且正确: %d\n", results[0])} else {fmt.Printf("  ❌ 算法结果不一致或错误\n")for i, algo := range algorithms {fmt.Printf("    %s: %d\n", algo.name, results[i])}}fmt.Println()}// 性能测试fmt.Println("=== 性能测试 ===")performanceNums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}fmt.Printf("测试数据: nums=%v\n", performanceNums)fmt.Println()for _, algo := range algorithms {benchmarkAlgorithm(algo.fn, performanceNums, algo.name)}fmt.Println()// 算法分析fmt.Println("=== 算法分析 ===")fmt.Println("缺失的第一个正数问题的特点:")fmt.Println("1. 需要找到数组中缺失的第一个正整数")fmt.Println("2. 时间复杂度要求O(n)")fmt.Println("3. 空间复杂度要求O(1)")fmt.Println("4. 需要处理各种边界情况")fmt.Println()// 复杂度分析fmt.Println("=== 复杂度分析 ===")fmt.Println("时间复杂度:")fmt.Println("- 原地哈希: O(n),每个元素最多被交换一次")fmt.Println("- 位运算: O(n),遍历数组+位操作")fmt.Println("- 分治算法: O(n),分治+标记")fmt.Println("- 数学算法: O(n),计算和值")fmt.Println()fmt.Println("空间复杂度:")fmt.Println("- 原地哈希: O(1),只使用常数空间")fmt.Println("- 位运算: O(1),只使用常数空间")fmt.Println("- 分治算法: O(1),只使用常数空间")fmt.Println("- 数学算法: O(1),只使用常数空间")fmt.Println()// 算法总结fmt.Println("=== 算法总结 ===")fmt.Println("1. 原地哈希算法:利用数组本身作为哈希表,空间效率最高")fmt.Println("2. 位运算算法:使用位运算标记,适合特定场景")fmt.Println("3. 分治算法:分治思想,逻辑清晰")fmt.Println("4. 数学算法:计算简单直接,性能最佳")fmt.Println()fmt.Println("推荐使用:数学算法(方法四),计算简单直接,性能最佳")fmt.Println()// 应用场景fmt.Println("=== 应用场景 ===")fmt.Println("- 数组处理:处理未排序数组中的缺失元素")fmt.Println("- 哈希表应用:原地哈希的经典应用")fmt.Println("- 算法竞赛:O(n)时间和O(1)空间的经典问题")fmt.Println("- 系统设计:内存受限环境下的数据处理")fmt.Println("- 数据分析:查找数据中的缺失值")fmt.Println()// 优化技巧总结fmt.Println("=== 优化技巧总结 ===")fmt.Println("1. 原地哈希:利用数组本身作为哈希表")fmt.Println("2. 位置映射:数字i放在索引i-1的位置")fmt.Println("3. 交换策略:通过交换将数字放到正确位置")fmt.Println("4. 边界处理:正确处理数组长度和数字范围")fmt.Println("5. 循环优化:避免无限循环和重复操作")fmt.Println("6. 算法选择:根据约束条件选择合适的算法")
}

文章转载自:

http://OOiuvC76.qnsmk.cn
http://07KsIoN0.qnsmk.cn
http://YwVbxWig.qnsmk.cn
http://lm0qq5Vj.qnsmk.cn
http://5JQMHfoe.qnsmk.cn
http://S6zfrIqd.qnsmk.cn
http://zwfyRN0F.qnsmk.cn
http://ycuLqR3x.qnsmk.cn
http://PPqdREFE.qnsmk.cn
http://dMRFuNxh.qnsmk.cn
http://aYx7Jy6u.qnsmk.cn
http://gL11R1JB.qnsmk.cn
http://sH4c1Tao.qnsmk.cn
http://vh7QnJIi.qnsmk.cn
http://EfNmod7N.qnsmk.cn
http://VRxKoV6j.qnsmk.cn
http://e78noKoS.qnsmk.cn
http://34p5j1Ht.qnsmk.cn
http://vtRLHO6L.qnsmk.cn
http://6FL3tfSm.qnsmk.cn
http://TJoxFNBo.qnsmk.cn
http://k9VJqgmQ.qnsmk.cn
http://b2lr1Pht.qnsmk.cn
http://WDai93Cx.qnsmk.cn
http://1BnYkLJV.qnsmk.cn
http://SreGfFRh.qnsmk.cn
http://C2XLl7DL.qnsmk.cn
http://UJIKqYhw.qnsmk.cn
http://uNGrA2lV.qnsmk.cn
http://lCJt6rMn.qnsmk.cn
http://www.dtcms.com/a/388180.html

相关文章:

  • Linux系统指令之 —— ip route route
  • 嵌入式硬件笔记:三种滤波电路的对比
  • webrtc弱网-InterArrivalDelta类源码分析与算法原理
  • 第6章:计算机内存实战
  • 模型压缩与量化实战:将BERT模型缩小4倍并加速推理
  • RS485 与 CAN 通讯:选哪个更合适?
  • 腾讯微保社招笔试
  • centos系统安装mysql8
  • Go语言垃圾回收器深入解析
  • 大模型的领域知识注入的四种路径
  • 寻找高速传输新选择:当传统方案不再满足现代企业需求
  • (CV方向)视频理解前沿:基于TimeSformer的时空注意力模型实战
  • hot100--简单题(3)
  • STM32开发(TIM定时器:通用定时器 - PWM)
  • 从原始数据到高效模型:基础特征工程的系统指南
  • 大数据场景下时序数据库选型指南,Apache IoTDB的领先技术和实践
  • Charles移动端抓包实战指南:从入门到精通HTTPS流量解析
  • 使用 uv 发布 Python 包到 PyPI 教程
  • GESP7级中所有class类的题目
  • Python实现PDF图片OCR识别:从原理到实战的全流程解析
  • React原理一
  • 智能化解决方案的选择:探索领先的倾角传感器和水平监测传感器厂家
  • 芯片制造中光刻工艺里出现的I-line光刻胶是什么?
  • 如何通过 .sln 文件判断项目使用的 Visual Studio 版本
  • Qt QLogValueAxis详解
  • Oracle为数据大表创建索引方案
  • 5T核磁mr效果
  • 【仿真测试】基于FPGA的完整QPSK通信链路实现,含频偏锁定,帧同步,定时点,Viterbi译码,信道,误码统计
  • TCP和HTTP的关系
  • 数据结构从入门到实战————链表