##
题目描述
Given an unsorted integer array, find the first missing positive integer.
For example,
Given[1,2,0]
return3
,
and[3,4,-1,1]
return2
.
Your algorithm should run in O(n) time and uses constant space.
###题目分析
这是一道看起来很简单却被标记为 Hard 的一道题。重点就是其时空复杂度被严格限制了。
首先先不考虑时空复杂度的限制, 若干个朴素的想法:
1.利用Hash表记录出现过的正数。
这种做法是我首先想到的。先扫描一遍数组得到正数的个数(不要拿最大值, 稍微会浪费一点),开相应大小的bool
数组。再扫描一遍数组, 利用bool
数组记录下对应信息, 最后再扫一遍数组即可。
时间复杂度:O(n) 空间复杂度: O(n)
2.利用二分法来逐步收小范围。
这种做法是我在考虑要求中空间复杂度的时候想到的。
既然题目对空间要求如此高。 自然考虑是不是只用若干个数来存是否有连续数段的信息。
从题目中不难得出如下推理:
设 任意 t > 0 , 该数组中的首个缺失正数为k, f(n)为前n个正数中存在的个数.
有: $ f(k - 1) = k - 1$, $ f(k) = k - 1$,
并有: $ f(k + t) <= f(k) + t = k - 1 + t < k + t $
依据此就很容易想到一个二分的解法:
- 扫描数组, 得到最大值。(顺便得个最小正数也不错。 如果不是1就不用继续做了 : )
- 取 $ Mid = \frac{Max + Min} {2}$ ,统计落在
[1,Mid]
的个数count
。 - 若
c
o
u
n
t
=
=
(
M
i
d
−
M
i
n
+
1
)
count == (Mid - Min + 1)
count==(Mid−Min+1), 说明前半段不存在间断。 更新
Min = Mid + 1
, 重复步骤2
若 c o u n t < ( M i d − M i n + 1 ) count < (Mid - Min + 1) count<(Mid−Min+1), 说明前半段存在间断。 更新Max = Mid
, 重复步骤2 - 以下步骤与二分法完全相似, 不赘述。
时间复杂度:
O
(
N
∗
l
o
g
(
N
)
)
O(N * log(N))
O(N∗log(N))
空间复杂度:
O
(
1
)
O(1)
O(1)
当然上面两种方法只是我的思考过程, 都不符合题目要求的。
3.合理利用给出的数组作为空间, 避免额外空间开销[正解]
PS:其实我觉得这题目一大坑就是敢不敢这样做。 反正一开始我看到传入参数是引用传递时, 根本没想过直接改数组。。
想法其实也没有多复杂: 就是利用数组下标与元素一一对应 。 不过不是按照之前所说的用bool值标识, 而是变成了直接用一个和下标有关的数值(事实上就是下标 + 1) 来标识。
举个具体例子:
[1,3,5,6,2]
变换为如下形式:
[1,2,3,6,5]
这样整理一遍数组, 再扫一遍即可。
整理过程呢, 就是遍历一遍数组, 然后把不对应的项 “试图" 交换到正确的位置上。为什么说是试图呢, 因为存在不可以交换的情况啊。比如负数。 事实上先预先扫一遍数组得到正数的个数可以把之后的可交换条件限制得更好一点。
还是举个例子:
[1,3,5,6,2]->[1,5,3,6,2]->[1,2,3,6,5]->[1,2,3,6,5]->[1,2,3,6,5]
上面每一步就是遍历完一个数组元素之后的结果。 1原本在正确的位置上, 不交换; 3 和 它对应位置上现存的元素(即nums[2] = 5
)交换. 注意这时候遍历在第二个元素上, 而事实上第二个元素现在存的元素仍旧不是正确的, 并且仍可交换 所以继续交换, 得到[1,2,3,6,5]
.之后到3, 正确; 到6, 无法交换(数组越界);最后到5, 正确。交换步骤完成。
这就是大体的思路了。 AC代码如下。 在这个思路上加了一点点小优化。
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int count = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] > 0) ++count;
}
if (count == 0) return 1;
int temp, t;
for (int i = 0; i < nums.size(); ++i) {
temp = i;
while (nums[temp] > 0 && nums[temp] <= count &&
nums[temp] != temp + 1 && nums[nums[temp] - 1] != nums[temp]) {
t = nums[nums[temp] - 1];
nums[nums[temp] - 1] = nums[temp];
nums[temp] = t;
}
}
for (int i = 0; i < count; ++i) {
if (nums[i] != i + 1) return i + 1;
}
return count + 1;
}
};
其他细节
- 注意处理一些特殊情况。 比如
[], [-1,0]
- 注意重复的时候不要让程序陷入死循环。如
[1,1],[2,2]
###The End.