题目:给定一个无序整型数组,找到数组中未出现的最小正整数。
例:arr=[-1,2,3,4] 返回1。
arr=[1,2,3,4] 返回5。
answer1:排序,找到第一个arr[i]!=i+1的数,未出现的最小正整数就是i+1. 如果数组遍历完,还是没有出现arr[i]!=i+1的数,
未出现的最小正整数是i+1(此时是第二个例子) ,但是,这不是最优解,因为排序的时间复杂度。
answer2:最优解可以达到时间复杂度为O(N)。参考 代码面试指南-左程云 书中的解法。
请耐心看!!!
请耐心看!!!
请耐心看!!!
【黑色是书中原述,红色是自己见解】
如果arr长度为N, 最优解可以做到时间复杂度O(N) 额外空间复杂度O(1)
1、遍历arr之前生成两个变量, left r 初始值 left=0 r=N
变量left的含义是遍历到目前为止,数组arr已经包含的整数范围是[1,left],所以没有开始
遍历之前令left=0,表示arr目前没有包含任何整数。
变量r的表示遍历到目前为止,在后续出现的最有状况的情况下,arr可能包含的整数范围是
[1,r],所以还没有开始遍历之前,令r=N,因为还没有开始遍历,所以后续出现的最有状况是arr
包含的1~N所以的整数,r同时表示arr当前的结束位置
(一个数组,如果要找数组中未出现的最小正整数,未出现的最小正整数的范围是什么?1~N+1,可自己想一下,
因为数组的长度只有N,
数组最优情况下,就是1,2,3,4,5
left 0 1 2 3 4
arr[left] 1 2 3 4 5 arr[left]=left+1; 未出现的最小正整数范围是1~6 未出现的最小正整数6
所以r开始所包含的整数范围是1~N
其他情况下,就是1,10,3,7,5
left 0 1 2 3 4
arr[left] 1 10 3 7 5 未出现的最小正整数范围是1~6 未出现的最小正整数2,不可能超过N+1
)
2、从左到右遍历arr,arr[left]
3、如果arr[left]=left+1 没有遍历arr[left]之前,arr已经包含的正整数范围是[1,left],
此时出现了arr[left]=left+1的情况,所以arr包含的正整数范围可以扩展到[1,left+1]
即令 left++
(步骤3,就是最优情况
i 0 1 2 3 4
arr[ i ] 1 2 3 4 5 arr[i]=i+1; 未出现的最小正整数范围是1~6 未出现的最小正整数6
遍历到i=2,遍历之前(i=1时,left的范围1~2)
遍历到i=2时,left的范围是1~3
)
4、如果arr[left]<=left 没有遍历arr[left]之前,arr在后续最优的情况下可能包含的
正整数范围是[left,r],已经包含了的正整数的范围是[1,left],所以需要[left+1,r]上的数。
而此时出现了arr[left]<=left,说明[left+1,r]范围上的数少了一个,所以
arr在后续最优的情况下,可能包含的正整数范围缩小了,变为[left,r-1],
此时把arr最后位置的数(arr[r-1])放在位置left上,下一步检查这个数,然后令r--,
(步骤4,5,6:出现的数都是不合法数,
arr[left]<=left,遍历到left时,应该是arr[left]=left+1,但是小于
arr[left]>r,arr[left]已经大于了数组的最大范围了(r表示数组的最大范围啊)
arr[left]=arr[arr[left]-1],重复了
遇到这三种情况,把arr最后位置的数(arr[r-1])放在位置left上,下一步检查这个数,然后令r--,
)
(单独说步骤4:
arr[left]<=left,遍历到left时,应该是arr[left]=left+1,但是小于,原本可以表示N个数的,你中间有个数都<left+1了,那该元素之后
可以表示的范围就是[left+1,r-1]了,不符合的数就不要了,把最后面的数替换了left,前面不是刚说吗,范围变小了r--
)
5、如果arr[left]>r,与上面同理的,把arr最后位置的数(arr[r-1])放在位置left上,
下一步检查这个数,然后令r--
(单独说步骤5:
和步骤4一样
)
6、如果arr[left]=arr[arr[left]-1],如果上面两个都没中,
说明 arr[left]是在[left+1,r]范围上的数,而且这个数应该放在arr[left]-1位置上,
可是此时发现arr[left]-1位置上的数已经是arr[left],
说明出现了两个arr[left]呀,既然在[left+1,r]上出现了
两个arr[left],重复了。那么[left+1,r]范围上的数又少了一个,
所以与步骤4和5一样,把arr[r-1]放在位置left上,下一步检查,然后另r--
(单独说步骤6:
和步骤4一样
)
7、 如果都没有中,说明发现了[left+1,r]范围上的数,并且没有重复。
那么arr[left]应该放在arr[left]-1位置上,
所以把left位置上的数和arr[left]-1位置上的数交换,下一步继续遍历left位置上的数
(这一步需要说明一下:到这一步,说明arr[left]=left+1,且不重复,且没有超出所能表示的最大值
所以arr[left]应该放在=arr[left]-1的位置上,left下标是从0开始的,arr[left]是从1开始的
)
最终left和r会碰撞在一起(left==r) arr已经包含的正整数范围是[1,left],
public int missNum(int[] arr){
int left =0;
int r = arr.length;
while(left<r){
if(arr[left]==left+1){
left++;
}else if(arr[left]<=left || arr[left]>r || arr[arr[left]-1]==arr[left]) {
arr[left]=arr[--r];
}else {
swap(arr,left,arr[left]-1);//就是两个互相交换
}
}
return left+1;
}
public void swap(int[] arr,int a,int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}