Leetcode1-100: 41. First Missing Positive
问题描述
题目要求: 输入一个整形数组,输出里面缺失的最小的正整数。
解题思路
由于题目中要求了时间复杂度是O(n),还要求空间复杂度是常数,所以这题变得难度大了很多。但是首先分析一下如果去掉这些要求,可以用一个额外的数组来作为flag表示状态的话,这题就简单了很多,注意:假设数组有n个元素,那么最后的结果一定是在1~n+1中产生的,因为n个位置最多放1~n,缺少任意一个都可以返回那个值,如果都在数组中的话解就是n+1
例子:
Input: [1,3,5,2,-1]
flag: [T,T,T,T,T]
初始状态都为True,走一遍Input数组,当读到1的时候把flag[0]变成False,
同理读到3把flag[2]换成False……最后flag数组变成了[F,F,F,T,F],
最后检验一遍flag数组,读到T在第四位,所以缺少了4.
现在再考虑使用常数项空间复杂度的方法,这时候就不能这么任性的用额外的数组了,那么就得保证在保存原来信息的前提下,改变输入数组本身来保存尽可能多的信息。那么两个方法产生了,最后为了便于判断,会把数组换成这个形式:数组的index位置存放的值是index+1(如果数组中有的话),否则说明这个位置对应的元素缺失了。例如:对于输入[2,5,1,3,92],应该保证最后2放在数组第二个元素位置,5在第五个,1在第一个,3在第三个,92不用管,那么两种实现的方法就是:
implement 1
当读取到一个正整数的时候,把它放到应该在的位置,然后把对应位置本来的值放到这个正整数的位置,重复这个操作直到没有值的改变即可。
例如,对于[3,4,5,-1,2]
,
第一次读到3,把3和5交换位置变成[5,4,3,-1,2]
,
然后5和2交换位置,得到[2,4,3,-1,5]
,
然后2和4交换位置,得到[4,2,3,-1,5]
,
然后4和-1交换位置,得到[-1,2,3,4,5]
此时无法再交换了,检查一下,index = 0的位置的值不是index+1,所以返回1
implement 2
除了交换位置,还可以把对应位置的元素变成负数来表示这个位置的值已经存在数组中了,那么就需要先处理一下数组,把所有负数先挪到后面去,来保证原本就是负数的元素不会干扰到flag的标记。
例如,对于[3,4,5,-1,2]
,
当读到-1的时候,把-1和2交换位置,使用count来表示数组中正整数的个数,count初始值是nums.length,每次读到非正整数的时候count -= 1
然后得到了结果: [3,4,5,2,-1],count = 4
然后开始读取,首先读到3,把index=2位置的5变成-5,表示index=2位置的值在数组中,然后读到4,把index=3位置对应的2变成-2,然后读到5,但是比count大所以不用管,然后读到2,把index=1位置对应的4变成-4,那么最后的结果就是[3,-4,-5,-2,-1],最后过一遍数组直到count位置,遇到正整数说明这个位置对应的值就不在数组中,输出index+1,如果没有读到正整数,就输出count+1即可
代码实现
implement 1
public int firstMissingPositive(int[] nums) {
//解法一:交换位置
for(int i = 0; i < nums.length; i++) {
if(nums[i] <= nums.length && nums[i] > 0 && nums[i] != i+1) {
int tmp = nums[i];
while(nums[i] <= nums.length && nums[i] > 0 && nums[i] != i+1 && nums[i] != nums[nums[i]-1]) {
tmp = nums[nums[i]-1];
nums[nums[i]-1] = nums[i];
nums[i] = tmp;
}
}
}
for(int i = 0; i < nums.length; i++)
if(nums[i] != i+1)
return i+1;
return nums.length + 1;
}
复杂度分析:
乍一看需要的时间复杂度是O(n2),但是实际上每个元素只被访问过一次,所以时间复杂度是O(n)
时间复杂度: O(n)
空间复杂度: O(1)
时间复杂度:O(n2)
空间复杂度:O(n2)
implement 2
public int firstMissingPositive(int[] nums) {
//解法二,负数标记法
int n = nums.length;
//首先把负数都放到后面去,然后得到正整数的数量放在变量n里面
for (int i = 0; i < n; i++) {
while (nums[i] <= 0) {
int temp = nums[i];
nums[i] = nums[n-1];
nums[n-1] = temp;
n--;
if (i == n) {
break;
}
}
}
if(n == 0) return 1;
//这里把所有在数组中的正整数的对应下标的元素变成负数
for(int i = 0; i < n; i++) {
if(Math.abs(nums[i]) <= n && nums[Math.abs(nums[i])-1] > 0)
nums[Math.abs(nums[i])-1] *= -1;
}
//最后找到正整数的位置
for(int i = 0; i < n; i++)
if(nums[i] > 0)
return i+1;
return n+1;
}
复杂度分析:
时间复杂度: O(n)
空间复杂度: O(1)
分析后记
这题比较有趣,也想了很久,最后发现大佬们的思路都是先把限制条件给去掉了做,然后分析怎么去用类似的思路来解决限制条件的问题。想想这样才是做题正常的想法,先去从本质看怎么做,然后再分析具体的实现方法。