算法题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; } }

浙公网安备 33010602011771号