每日一题——缺失的第一个正数

问题描述

给定一个未排序的整数数组nums,请你找出其中没有出现的最小正整数。要求实现时间复杂度为O(n),并且只使用常数级别的额外空间。

示例
  1. 输入: nums = [1, 2, 0]
    输出: 3
    解释: 范围 [1, 2] 中的数字都在数组中,缺失的最小正整数为3。

  2. 输入: nums = [3, 4, -1, 1]
    输出: 2
    解释: 数组中包含1,但缺失2。

  3. 输入: nums = [7, 8, 9, 11, 12]
    输出: 1
    解释: 最小的正整数1没有出现。

输入与输出
  • 输入:一个整数数组nums,长度在[1, 10^5]范围内,数组中的每个元素范围为[-2^31, 2^31 - 1]
  • 输出:一个整数,表示数组中缺失的最小正整数。

解题思路

为了在O(n)时间复杂度和常数空间复杂度下解决问题,我们可以利用数组的索引作为辅助信息。具体步骤如下:

  1. 原地交换:
    将数组中的每个正整数放到其对应的索引位置。例如,数字1应该放到索引0的位置,数字2应该放到索引1的位置,依此类推。

  2. 遍历数组:
    遍历数组,找到第一个不满足nums[i] == i + 1的索引,返回i + 1作为缺失的正整数。

  3. 特殊情况:
    如果所有数字都正确,返回numsSize + 1


代码实现

以下是C语言的实现代码,包含详细注释:

int firstMissingPositive(int* nums, int numsSize) {
    // 第一轮循环:将每个正整数放到其对应的索引位置
    for (int i = 0; i < numsSize; i++) {
        // 使用 while 循环确保每次交换后重新检查当前 nums[i]
        while (nums[i] > 0 && nums[i] <= numsSize && nums[nums[i] - 1] != nums[i]) {
            // 交换 nums[i] 和 nums[nums[i] - 1]
            int tmp = nums[nums[i] - 1];
            nums[nums[i] - 1] = nums[i];
            nums[i] = tmp;
        }
    }

    // 第二轮循环:查找第一个不满足 nums[i] == i + 1 的索引
    for (int i = 0; i < numsSize; i++) {
        if (nums[i] != i + 1) {
            return i + 1; // 返回缺失的正整数
        }
    }

    // 如果所有数字都正确,返回 numsSize + 1
    return numsSize + 1;
}

代码解析

1. 原地交换逻辑
while (nums[i] > 0 && nums[i] <= numsSize && nums[nums[i] - 1] != nums[i]) {
    int tmp = nums[nums[i] - 1];
    nums[nums[i] - 1] = nums[i];
    nums[i] = tmp;
}
  • 条件解析:
    • nums[i] > 0 && nums[i] <= numsSize:确保当前数字是有效的正整数,并且在数组范围内。
    • nums[nums[i] - 1] != nums[i]:避免重复交换,确保交换操作有意义。
  • 交换逻辑:
    • 使用临时变量tmp保存nums[nums[i] - 1]的值。
    • nums[i]放到其正确的位置nums[nums[i] - 1]
    • tmp赋值给nums[i],完成交换。
2. 查找缺失的正整数
for (int i = 0; i < numsSize; i++) {
    if (nums[i] != i + 1) {
        return i + 1; // 返回缺失的正整数
    }
}
  • 遍历数组,检查每个数字是否在其正确的位置。
  • 如果发现nums[i] != i + 1,则返回i + 1作为缺失的正整数。
3. 特殊情况
return numsSize + 1;
  • 如果所有数字都正确,说明缺失的正整数是numsSize + 1

示例分析

示例1

输入: nums = [1, 2, 0]
过程:

  1. 第一轮循环后,数组变为[1, 2, 0](无需交换)。
  2. 第二轮循环中,nums[2] = 0 != 3,返回3

输出: 3

示例2

输入: nums = [3, 4, -1, 1]
过程:

  1. 第一轮循环后,数组变为[1, -1, 3, 4]
  2. 第二轮循环中,nums[1] = -1 != 2,返回2

输出: 2

示例3

输入: nums = [7, 8, 9, 11, 12]
过程:

  1. 第一轮循环后,数组仍为[7, 8, 9, 11, 12](无需交换)。
  2. 第二轮循环中,nums[0] = 7 != 1,返回1

输出: 1


注意事项

错误的交换顺序
int tmp = nums[i];
nums[i] = nums[nums[i] - 1];
nums[nums[i] - 1] = tmp;

问题:
在第二步中,nums[i] 被修改了,导致第三步的索引 nums[i] - 1 变成了错误的值。因为 nums[i] 已经被改过,所以后续的交换会出错。

正确的交换顺序
int tmp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = tmp;

正确原因:
在第一步中,先保存了目标位置的值 nums[nums[i] - 1]
在第二步中,直接把 nums[i] 放到目标位置。
在第三步中,把之前保存的值赋给 nums[i]
关键: 这样不会提前修改 nums[i],所以索引始终是正确的。

核心区别
  • 错误的交换:先改了 nums[i],导致后续索引错乱。
  • 正确的交换:先保存目标位置的值,再交换,不会错乱。

简单来说,就是不要提前修改 nums[i],否则索引会乱掉

  1. 边界条件:

    • 确保交换时的索引nums[i] - 1始终在有效范围内([0, numsSize - 1])。
  2. 时间复杂度:

    • 每个数字最多被交换两次(一次放到正确位置,一次被交换出来),因此总时间复杂度为O(n)。

总结

通过原地交换的方式,我们可以高效地将数组中的正整数放到其对应的位置,并通过一次遍历找到缺失的最小正整数。这种方法满足了O(n)时间复杂度和常数空间复杂度的要求,适用于解决此类问题。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tt555555555555

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值