算法题15:缺失的第一个正数

题目描述

给你一个未排序的整数数组 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到n。

有n个学生坐在教室的座位上,把nums[i]当作坐在第i个位置上的学生的学号。我们要做的事情就是让学号在1到n中的学生,都坐到编号与自己学号相同的座位上。

学号不在[1,n]中的学生可以忽略。

 

学生们交换座位后,从左到右看,第一个学号与座位编号不匹配的学生,其座位编码就是答案。

特别地,如果所有学生都坐在正确的座位上,那么答案是n + 1。

第一个例子:

为方便描述,假设数组的下标是从1开始的。

假设nums = [2,3,1]。

1、从nums[1]开始。这个座位上的学生,学号是2,他应当坐在nums[2]上,所以他和nums[2]交换。交换后nums = [3, 2, 1]。

2、仍然看nums[1],这个座位上的学生,学号是3,他应当坐在nums[3]上,所以他和nums[3]交换,交换后nums = [1, 2, 3]。

3、仍然看nums[1],这个座位上的学生,学号是1,他坐在正确的座位上。

4、向右遍历,nums[2] = 2,他坐在正确的座位上。

5、向右遍历,nums[3] = 3,他坐在正确的座位上。

6、换座位过程结束。

7、再次遍历nums,发现nums[i] = i都满足,说明数组中1, 2, 3都有,所以缺失的第一个正数是4。

 

第二个例子

假设nums = [3, 4, -1, 1],这是题目中的示例2。

1、从nums[1]开始。这个座位上的学生,学号是3,他应当坐在nums[3]上,所以他和nums[3]交换。交换后nums = [-1, 4, 3, 1]。

2、仍然看nums[1],这个座位上的学生,学号是-1,忽略。

3、向右遍历,nums[2] = 4,他应当坐在nums[4]上,所以他和nums[4]交换。交换后nums = [-1, 1, 3, 4]。

4、仍然看nums[2] = 1,他应当坐在nums[1]上,所以他和nums[1]交换。交换后nums = [1, -1, 3, 4]。

5、仍然看nums[2],这个座位上的学生,学号是-1,忽略。

6、向右遍历,nums[3] = 3, 他坐在正确的座位上。

7、向右遍历,nums[4] = 4,他坐在正确的座位上。

8、换座位过程结束。

9、再次遍历nums,发现nums[2] = -1 ≠ 2,说明教室中没有学号为2的学生(否则他会坐在nums[2]上),所以答案是2。

第三个例子

注意nums中可能有重复元素。在这种情况下,算法仍然是正确的吗?

 

假设nums = [1, 1, 2]。

1、从nums[1]开始。这个座位上的学生坐在正确的座位上。

2、继续遍历,nums[2] = 1,这是1号学生的影分身。由于1号学生的真身已经坐在正确的座位上,我们可以在第二次遍历中知道【数组中有1】这个信息,

所以可以忽略nums[2],向右遍历。

3、nums[3] = 2,他应当作为nums[2]上,所以他和nums[2]交换。交换后nums=[1, 2, 1]。

4、仍然看nums[3] = 1,同样地,由于1号学生已经坐在正确的座位上,所以可以忽略nums[3]。

5、换座位过程结束。

6、再次遍历nums,发现nums[3] = 1 ≠ 3,说明教师中没有学号为3的学生,所以答案是3.

细节

 判断学生是否坐在正确的座位上,能用nums[i] = i判断吗?注意有影分身(重复元素)。

在第三个例子中,虽然nums[2] = 1 ≠ 2,但由于nums[nums[2]] = nums[1] = 1,所以nums[2]是个影分身,且其真身坐在了正确的位置上,所以可以忽略nums[2],

向右遍历。注意这种情况是不能交换,因为nums[2] = nums[1],交换后nums = [1,1,2]是不变的,会导致死循环。

为了避免死循环,可以改成判断nums[2] 和nums[nums[2]]是不是一样的。如果一样就不交换,继续向右遍历。

为了兼容“当前学生是真身,坐在正确的座位上”,和“当前学生是影分身,且其真身坐在了正确的座位上”两种情况,我们可以把i = nums[i]套一层nums,

用nums[i] = nums[nums[i]判断。

 

 

python:

class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        #
        n = len(nums)
        for i in range(n):
            # 如果当前学生的学号在[1, n]中,但(真身)没有坐在正确的座位上
            while 1 <= nums[i] <= n and nums[i] != nums[nums[i] - 1]:
                # 交换nums[i] 和nums[j],其中j是i的学号
                j = nums[i] - 1 # 减一是因为数组下标从0开始
                nums[i], nums[j] = nums[j], nums[i]
        
        # 找第一个学号与座位编号不匹配的学生
        for i in range(n):
            if nums[i] != i + 1:
                return i + 1
        # 所有学生都坐在正确的座位上
        return n + 1

 

java

class Solution {
    public int firstMissingPositive(int[] nums) {
        int n  = nums.length;
        for (int i = 0; i < n; i++) {
            // 如果当前学生的学号在[1, n]中,但(真身)没有坐在正确的座位上
            while (1 <= nums[i] && nums[i] <= n && nums[i] != nums[nums[i] - 1]) {
                // 那么就交换nums[i]和nums[j],其中j是i的学号
                int j = nums[i] - 1; // 减一是因为数组下标从0开始
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j] = tmp;
            }
        }

        // 找第一个学号与座位号不匹配的学生
        for (int i = 0; i < n; i ++) {
            if (nums[i] != i + 1){
                return i + 1;
            }
        }
        // 所有学生都坐在正确的座位上
        return n + 1;
        
    }
}

 

posted @ 2025-06-08 10:37  夏晓旭  阅读(39)  评论(0)    收藏  举报