题目: Given an unsorted integer array, find the first missing positive integer.
For example,
Given [1,2,0]
return 3
,
and [3,4,-1,1]
return 2
.
Your algorithm should run in O(n) time and uses constant space.
为什么这道题值得纪念呢? 因为它教育我们看问题看本质。
要看出问题本质,首先要深刻理解问题本身是在说啥。。。(越来越像学习某某core的讲话了)
在一个无序的整数数组中,找出第一个没有的正数。什么意思呢?这么想,所有正数是1,2,3,4,5,....(把8倒过来). 找出最小的在这个数组中没有的数。而且人家要O(n),那说明你就没法用排序算法了。但是你肯定想到用hash是不是。一般来说,切题用hash都是没有办法的办法,不过人说了,“uses constant space“,hash都没法用了是不。
我遇到这样的题第一个本能的想法是用bit位移来做。这其实是有些讨巧的hash方法,相当于hash表的长度就只有一个整数大小。首先扫描数组,如果遇到正数i,就把该某个int值的i位置1。最后扫描那个int值,找出第一个0值。也是O(n)有木有。。。
代码是酱紫的。
解法一:
1 public static int firstMissingPositive(int[] A) { 2 // Start typing your Java solution below 3 // DO NOT write main() function 4 int bits = 0; 5 for(int i = 0; i < A.length; i++){ 6 if(A[i] > 0){ 7 bits |= (1 << (A[i] - 1)); 8 } 9 } 10 int idx = 0; 11 while(bits%2 != 0){ 12 bits = (bits >> 1); 13 idx++; 14 } 15 return idx + 1; 16 }
我觉得也符合条件呀,然后大集合就废了。超时超时。。。。
解法二:
我发现大牛们的有几个共同的特点。1. 比较快速地看问题本质;2. 可能切题很多,比较容易举一反三。
比如这个题,假设数组长度是n。那么第一个missing的正数肯定不会超过n啊混蛋。如果把所有正数都放在正确的位置上,数字1(如果有的话)在A[0], 数字2(如果有的话)在A[1],数字i + 1在A[i],那么我们只要扫描A,遇到第一个A[i]不等于i+1的,就知道这个missing的正数(i+1)了。大家可以用上面的例子仔细想想这个道理。但是怎么在O(n)时间内把数字i + 1(如果有的话)移动到A[i]呢?我估计大牛们肯定早就知道这个算法了。“范围内整数的线性排序”,我之前也在wiki上见过。就是把1-n的整数放置到长度为n的数组中,有序,in-place。
具体方法是,扫描数组A,如果当前的正数A[i]不在其该在的位置,那和A[A[i]-1]交换(这样交换过后A[i]-1的正数就in postion了。继续,直到i位置上的正数是正确的。然后继续下一个交换。
这样总能把所有正数in position。
差距,差距呀。。。
先上代码。
1 public static int firstMissingPositive2(int[] A){ 2 for(int i = 0; i < A.length; i++){ 3 if(A[i] > 0 && A[i] <= A.length){ 4 if(A[i] != i + 1 && A[A[i] - 1] != A[i]){ 5 int tmp = A[A[i] - 1]; 6 A[A[i] - 1] = A[i]; 7 A[i] = tmp; 8 i--; 9 } 10 } 11 } 12 13 for(int i = 0; i < A.length; i++){ 14 if(A[i] != i+1){ 15 return i+1; 16 } 17 } 18 return A.length + 1; 19 }
这里在swap之前判断了两个条件,因为数组中并不一定包含所有的可以in-postion的正数。
总结:
1)还是题做得不够多;
2)下笔前分析透彻。