刷题笔记:力扣第41题-缺失的第一个正数

1.本题首先想到的方法便是哈希表,但本题要求空间复杂度为O(1),实在想不出别的方法,所以选择去理解答案。

2.第一种答案的方法本质上也是哈希表思想,只不过是利用原数组来存储信息,利用负号来做标记,它的完整思路如下:

(1)对于一个大小为N的数组,缺失的第一个正数一定在1, N + 1范围内,当数组中的元素为1, N时答案才会为N + 1。该数组中会出现一些我们不需要关心的垃圾值,即非正数以及大于N的数,所以第一步应该是清除垃圾值。遍历一遍数组,将这些值全部变为N + 1(或其他不需要关心的值)。

cpp 复制代码
1. for (int i = 0; i < numsSize; i++){
2.     if (nums[i] <= 0) nums[i] = numsSize + 1;
3. }

(2)之后就开始进行标记,再次遍历数组,当遍历到的元素为需要关心的值时,将该元素值-1的位置标记为负数。需要注意,每次取值的时候要取绝对值num = abs(numsi),避免该位置已经被标记过负数;且在将numsnum - 1标记为负数时也不要忘记先取绝对值再进行标记。

cpp 复制代码
1. for (int i = 0; i < numsSize; i++){
2.     int num = abs(nums[i]);
3.     if (num <= numsSize) nums[num - 1] = -abs(nums[num - 1]);
4. }

(3)最后再遍历一遍数组,当遍历到第一个正数时,取它的下标+1即为答案。若遍历一遍后都没有得出答案,则说明遇到了特殊情况,答案即为N + 1。

cpp 复制代码
1. for (int i = 0; i < numsSize; i++){
2.     if (nums[i] > 0) return i + 1;
3. }

3.基于以上思想,可写出完整代码如下:

cpp 复制代码
 1. // 寻找缺失的第一个正整数,原地数组标记法
 2. int firstMissingPositive(int* nums, int numsSize) {
 3.     // 将所有非正数修改为numsSize+1,不参与后续标记
 4.     for (int i = 0; i < numsSize; i++){
 5.         if (nums[i] <= 0) nums[i] = numsSize + 1;
 6.     }
 7.     // 取绝对值得到数字,在对应下标位置标记负数,表示该数字存在
 8.     for (int i = 0; i < numsSize; i++){
 9.         int num = abs(nums[i]);
10.         if (num <= numsSize) nums[num - 1] = -abs(nums[num - 1]);
11.     }
12.     // 遍历数组,第一个正数对应的下标+1就是缺失的最小正整数
13.     for (int i = 0; i < numsSize; i++){
14.         if (nums[i] > 0) return i + 1;
15.     }
16.     // 1~numsSize全部存在,缺失的是numsSize+1
17.     return numsSize + 1;
18. }

该算法时间复杂度为O(n),空间复杂度为O(1)。

4.第二种方法是置换法。将每个需要关心的值都送到它对应值下标-1的位置,最后再遍历一遍数组,发现哪里位置不匹配,则下标+1即为答案。完整代码如下:

cpp 复制代码
 1. int firstMissingPositive(int* nums, int numsSize) {
 2.     for (int i = 0; i < numsSize; i++){
 3.         while (nums[i] > 0 && nums[i] <= numsSize && nums[i] != nums[nums[i] - 1]){
 4.             int tmp = nums[nums[i] - 1];
 5.             nums[nums[i] - 1] = nums[i];
 6.             nums[i] = tmp;
 7.         }
 8.     }
 9.  
10.     for (int i = 0; i < numsSize; i++){
11.         if (nums[i] != i + 1) return i + 1;
12.     }
13.  
14.     return numsSize + 1;
15. }

该算法时间复杂度为O(n),空间复杂度为O(1)。

5.该方法需要注意两点:

(1)第一次for循环中换位判断需要使用while,因为换来的数仍可能是需要关心的值。且为了避免出现需要交换的两数数值相同导致出现死循环,需要额外加上numsi != numsnums\[i - 1]的条件,将重复的那个数也当成垃圾值处理。

(2)循环交换代码只能按照给出的代码中那样的顺序写,不能写成如下形式:

cpp 复制代码
1. int tmp = nums[i];
2. nums[i] = nums[nums[i] - 1];
3. nums[nums[i] - 1] = tmp;

因为numsi的值不仅是待交换值,还会参与到后面numsnums\[i - 1]的计算中。如果先把numsi的值进行了修改,就会导致numsnums\[i - 1]的numsi -- 1下标出现变化导致错误!

相关推荐
shehuiyuelaiyuehao1 小时前
算法50,分治归并,数组中的逆序对
java·算法
自不量力的A同学1 小时前
开源一站式智能终端 uniTerm v1.9.4 发布
笔记
阳明山水2 小时前
校准分位数驱动智能库存决策
人工智能·深度学习·算法·机器学习·架构
91刘仁德2 小时前
C++ 继承和多态 设计模式
c语言·c++·笔记
库玛西3 小时前
哈夫曼树与前缀编码:数据压缩的贪心核心
c语言·c++·笔记·考研
别动我齐刘海3 小时前
ROS2 Jazzy + C++ 实战路线——进阶学习3
c++·人工智能·vscode·python·算法·机器学习·机器人
zhangfeng11333 小时前
《从“人工适配“到“智能生成“:KernelSwift 跨国产芯片算子迁移全栈方案解读》 —— 强调范式跃迁和跨硬件属性,适合偏架构分析的写法
人工智能·算法·华为·ai编程·npu
0+1113 小时前
算法 --滑动窗口
c++·算法·leetcode
reikocao4 小时前
大场景下 Hessian 特征值放大与退化检测
算法