刷题笔记:力扣第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下标出现变化导致错误!

相关推荐
2601_9707038318 分钟前
工业除湿机租赁:把一份租金合同拆开看
大数据·数据结构·算法·数据库架构·推荐算法
心中有你021419 分钟前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法
ZhengEnCi1 小时前
同样的模型,为什么本地和云 API 差别那么大:量化精度、聊天模板与采样参数的三大元凶
人工智能·算法·llm
Flynn Liang2 小时前
RAG 答非所问/幻觉怎么破?Spring AI QuestionAnswerAdvisor 召回优化实战(TopK/重排/混合检索)
人工智能·算法·spring·rag·幻觉·springai
liminla!2 小时前
SQL 从入门到进阶:语法速查与实战练习(完整优化版)
数据库·sql·算法
维克兜率天3 小时前
【维克】指数调整:哪些股票会被“强制买入“?
人工智能·python·算法
罗西的思考4 小时前
【轻量化复刻大模型】线性注意力交互式演示
人工智能·算法·机器学习
神也畏惧菠萝4 小时前
Day08 Python流程控制从入门到上瘾:if/match/while/for四件套,从B站登录一路打到猜数字游戏
python·算法·数据存储·for·while·if·ifelse
箓维4 小时前
TCP Socket的进阶实现
网络·笔记·tcp/ip