数组中未出现的最小正整数

题目:给定一个无序整型数组,找到数组中未出现的最小正整数。

例: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;
    }

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值