Given an unsorted integer array, find the smallest missing positive integer.
Example 1:
Input: [1,2,0]
Output: 3
Example 2:
Input: [3,4,-1,1]
Output: 2
Example 3:
Input: [7,8,9,11,12]
Output: 1
Note:
- Your algorithm should run in O(n) time and uses constant extra space.
Solution: 拨乱反正
https://www.tianmaying.com/tutorial/LC41
解法
这道题目是一道非常有意思的题目,它的难处在于极其苛刻的时间复杂度O(n)和空间复杂度O(1),如果没有这样的限制,那么无论是排序还是Hash都是非常好的解决方法,但这就是现实之所以残酷的地方~
对于这样苛刻的要求,我们不免产生这样的想法:会不会这样的问题存在一个非常好的性质,使得只需要扫描一遍数组就可以计算出来呢? 答案是否定的,并不可能就只使用这样的条件来完成计算。
那么是否说明这道题目就不能够完成呢?也不是,我们需要注意到一点,虽然输入的时间和空间复杂度都在“传入参数”的使用中不被考虑,但是这也意味着我们可以利用这传入参数本身占用的空间,即利用参数nums数组来帮助我们的计算,这样我们或许就可以拥有一个“不完整”的O(n)的空间复杂度了。
笔者注:这样的技巧——即使用传入参数本身带有的空间,无疑是一种非常巧妙地技巧,但是笔者在这里认为,这样的技巧仅仅只是一种字面意义上的“技巧”而已,在一般的竞赛环境中,由于输入输出是使用标准输入输出进行的,所以就无法使用这样的技巧(同理,那些使得时间复杂度降低到比输入复杂度还低的技巧也是无用的),而在一般的工作场景中,这样的对传入参数的修改可能带来无法估计的破坏,所以在使用这样的技巧解题的时候一定要明白它仅仅是一种技巧而已,不要过于崇尚这样的技巧。
于是,我们现在就等于拥有了O(n)的时间复杂度和O(n)的空间复杂度来解决我们的问题,虽然在使用空间的时候需要注意不能够破坏掉那些我们仍然需要的信息,但是我们仍然有不少的方法来解决这个问题,下面笔者介绍其中最为奇妙的一种,其它的方法,就有待读者自己去进行开发了(只需要铭记这类方法的本质都是利用nums数组来存储一些额外的信息即可)
这个方法的思路是这样的:
由于寻找的是“最小的未出现于nums中的正整数”,所以我们不难发现,nums中一定存在这样的一系列数1,2,3,4,...,k,而我们最后的答案就是k+1
也就是说,如果我们抛开负数不看的话,将nums排序之后,第一个不满足“nums[i]==i+1”的位置,就是我们寻找的答案
值得庆幸的是,对于nums的排序实际上只有对于其中“1,2,3,4,...,k”的部分有意义,所以我们可以采取桶排序的思路,即对于每一个nums[i],如果它的值在[1, nums.size()], 则将其与下标为nums[i]-1的数进行交换,即将“1”这个数字放置于nums的第1个位置,将“2”这个数字放置于nums的第2个位置,依次类推。由于在这个过程中采用的是交换,所以可以保证处理完后的nums和之前的nums拥有相同的数字集。
在这样的交换全部完成后,我们便会发现nums存在这样的性质:nums[0]==1, nums[1]==2, ...,而第一个不符合这样性质的nums[i]就是答案。
而负数和那些大于nums.size()的数一样,是不会对这个性质产生任何影响的。
这个方法的核心思路正是我们上面所说利用nums数组来存储一些额外的信息,由于nums代表的是一个集合,所以它的顺序是无所谓的,而我们就可以利用这些顺序带来的额外信息,帮助我们的计算。
class Solution {
public int firstMissingPositive(int[] nums) {
if (nums == null || nums.length == 0)
return 1;
int len = nums.length;
for (int index = 0; index < len; index ++) {
while (nums[index] > 0 && nums[index] <= len && nums[index] != nums[nums[index] - 1]) {
swap (nums, index, nums[index] - 1);
}
}
for (int index = 0; index < len; index ++) {
if (nums[index] != index + 1)
return index + 1;
}
return nums.length + 1;
}
public void swap (int[] nums, int first, int second) {
if (nums == null || nums.length == 0 || first < 0 || first >= nums.length || second < 0 || second >= nums.length)
return;
int temp = nums[first];
nums[first] = nums[second];
nums[second] = temp;
}
}