这道题目要求我们在未排序的整数数组中找到缺失的最小正整数,并且需要在O(n)时间复杂度和常数空间复杂度下完成。乍一看似乎很简单,但实际解决起来需要一些巧妙的思路。
首先我们需要明确几个关键点:
最直观的解法可能是:
我们需要一种原地(in-place)的解决方案。这里可以采用"原地哈希"的思路:
让我们仔细分析给出的解法代码:
cpp复制class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// 第一次遍历:将每个数字放到正确的位置
for (int i = 0; i < n; ++i) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums[nums[i] - 1], nums[i]);
}
}
// 第二次遍历:检查第一个位置不正确的数字
for (int i = 0; i < n; ++i) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return n + 1;
}
};
这个while循环做了以下几件事:
nums[i] > 0:只处理正整数nums[i] <= n:只处理可能在1-n范围内的数(因为数组长度n,最多需要n个正整数)nums[nums[i] - 1] != nums[i]:如果当前数字不在它应该在的位置当满足这三个条件时,我们把当前数字交换到它应该在的位置(即数字x应该放在索引x-1处)
注意:这里使用while而不是if是因为交换后,新的nums[i]可能也需要被处理
经过第一次遍历后,所有在1-n范围内的正整数都应该被放在正确的位置。因此:
虽然代码中有嵌套循环,但每个数字最多被交换一次到正确位置,所以总操作次数是O(n):
只使用了常数个额外变量(n, i等),满足O(1)空间复杂度要求。
让我们用示例2 nums = [3,4,-1,1] 来逐步演示:
初始状态:[3,4,-1,1]
第一次遍历:
最终数组:[1,-1,3,4]
第二次遍历:
另一种思路是将不符合条件的数字标记为特定值(如n+1),然后使用符号位作为标记:
cpp复制int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// 将非正数标记为n+1
for (int i = 0; i < n; ++i) {
if (nums[i] <= 0) nums[i] = n + 1;
}
// 使用符号位标记出现过的数字
for (int i = 0; i < n; ++i) {
int num = abs(nums[i]);
if (num <= n) {
nums[num - 1] = -abs(nums[num - 1]);
}
}
// 找到第一个正数位置
for (int i = 0; i < n; ++i) {
if (nums[i] > 0) return i + 1;
}
return n + 1;
}
在Python中实现时要注意:
这种算法虽然看起来是纯理论题目,但实际上有重要应用:
理解这种原地算法对于处理大数据量且内存受限的场景特别有价值。
这个问题可以扩展为:
这些扩展问题在实际工程中可能更有意义,但都需要建立在对基础算法的深刻理解之上。
在实际实现这个算法时,有几点特别值得注意:
这个算法很好地展示了如何利用问题本身的约束条件(数组长度n与要找的数范围1-n的关系)来设计巧妙的原地算法。掌握这种思路对于解决其他类似的算法问题很有帮助。