第一个缺失的整数

第一个缺失的整数

给定一个长度为N的数组A[0,…N-1],从1开始,找到第一个不在数组中的正整数。
如 [3,9,8,1,6,32]输出为2。
思路:
求解该问题可以有三种策略,分别是暴力破解法、BitMap法和循环不变式方法。
方法1—暴力破解法:
看到这个题后,既然是找从1开始第一个不在数组A中的正整数,那最简单最直接的反应就是我能否可以从1开始,在A中查找这个数是否存在,若存在则找下一个正整数,知道找到第一个不在A中的正整数。可以很明显可知,该算法的时间复杂度为O(n2)O(n^2)O(n2),空间复杂度为O(1)O(1)O(1),显然这不是我们想要的结果。
那么,我们进一步想,如果A是有序的话,则从头开始查找,找到第一个A[i]!=i(为简单讲解,假设数组的下标是从1开始,下同)的位置,则i即为要找的数字。因为给定的数组A并不有序,则需要首先对A进行排序,然后在对A进行遍历即可。则很明显,该算法时间复杂度主要花在对A的排序上,即使利用时间复杂度最低的排序算法,其时间复杂度仍为O(n∗logn)O(n*logn)O(nlogn),空间复杂度为O(1)O(1)O(1).
由上可知,暴力破解法的时间复杂度太高。
方法2—BitMap法:
所谓的BitMap就是用一个bit位来标记某个元素所对应的value,而key即是该元素。由于题目中并没有对算法的空间复杂度进行要求,所以我们可以进行以下操作:
首先申请一个与AAA长度一样的空数组B=[1,...,N]B=[1,...,N]B=[1,...,N] (假设数组BBB下标是从111开始),并将所有位置初始化为−1-11。然后从头遍历AAA,将A[i]A[i]A[i]映射到BBB中,具体映射方式为如果A[i]&lt;1A[i]&lt;1A[i]<1 或者A[i]&gt;NA[i]&gt;NA[i]>N则舍弃,否则B[A[i]]=A[i]B[A[i]]=A[i]B[A[i]]=A[i]。映射完成后,从111开始遍历数组BBB,找到第一个B[i]!=iB[i]!=iB[i]!=i的位置,iii即为所求。
然而这是为什么呢?为什么可以这样做?
这是因为题目是从1开始找到第一个不在数组中的正整数,因为一个萝卜一个坑,只要A数组中存在一个数大于N或者小于1,那么可以很容易得出这个缺失的正整数zzz一定满足z&gt;=1并且z&lt;=Nz&gt;= 1 并且 z&lt;=Nz>=1z<=N
或者如果AAA数组中的数都介于111NNN之间且互不重复,则z必等于N+1N+1N+1
所以,上述映射和查找方式是成立的。
BitMap法花费的总时间为2N2N2N,空间大小为NNN,因此BitMap算法的时间复杂度为O(n)O(n)O(n),空间复杂度也为O(n)O(n)O(n)
方法3—循环不变式法:
因为BitMap的时间复杂度已经是O(n)O(n)O(n)数量级,对于此类查找算法已经不可能有数量级上的优化,即使优化也只是降低系数。但是BitMap算法的空间复杂度也是O(n)O(n)O(n),那这个空间复杂度是否可以降低呢?
其实,归根到底,上述BitMap方法只是在做一件事,那就是将每一个数字都放到它应该在的位置上去。既然是这样,我们其实可以不用申请空间,而直接对原数组进行操作。即从前向后遍历数组A,将A中的每一个数放到对的位置,知道某一个位置的数据一直没有被找到,那么这个位置上的数据即为所求,上面这句话比较不好理解,那我们形式化表示一下:

假定前i−1i-1i1个数已经找到,并且依次存放在A[1,2,.....,i−1]A[1,2,.....,i-1]A[1,2,.....,i1]中,继续考察A[i],有三种情况:
(1) 若A[i]A[i]A[i]<i 且 A[i]&gt;=1[i]&gt;=1[i]>=1,则A[i]在A[1,2,…,i-1]中已经出现过,可以直接丢弃。
ps:若A[i]为负,则更应该丢弃它。
(2)若A[i]>i且A[i]<=N,则A[i]应该位于后面的位置上,则将A[A[i]]和A[i]交换。
ps:若A[i]>=N,由于缺失数据一定小于N,则丢弃A[i]。
(3)若A[i]=i,则A[i]位于正确的位置上,则i加1,循环不变式扩大,继续比较后面的元素。

对上面描述进行整理可得:
(1) 若A[i]<i 或者A[i]>N,则丢弃A[i]
(2) 若A[i]>i,则将A[A[i]]和A[i]交换。
(3) 若A[i]=i,则i加1,继续比较后面的元素。
至此,整个的算法描述已经清楚了。

那么现在有个问题,即如何对A[i]进行丢弃呢?
倒是可以直接多数组中这个位置进行删除,然后后面的所有元素往前移动,不过这样子也太费时间了。
我们可以参考堆排序或者树的剪枝操作,直接用最后一个元素A[A.size()-1]去替换A[i],然后A的长度减1。这样子就相当于对A[i]进行了丢弃,并且也不需要进行大量的其它操作,就是进行一个逻辑删除。

好了,至此整个算法的思路已经讲清楚了,下面附上循环不变式解法的python代码,供参考。

# -*- coding: utf-8 -*-
"""
Created on Sun Jan 20 15:04:13 2019

@author: shuaifeng
"""

def findFirstMissingInteger(tableA):
    if tableA is None or len(tableA)==0:
        return 1
    
    endindex=len(tableA)
    index=0
    while index<endindex:
        if tableA[index]>endindex or tableA[index]<index+1: #第一种情况,直接丢弃
            tableA[index]=tableA[endindex-1]
            endindex=endindex-1
        elif tableA[index]>index+1: #将数值放到正确的位置上去
            temp=tableA[tableA[index]-1]
            tableA[tableA[index]-1]=tableA[index]
            tableA[index]=temp
        else: #当前位置的数据在正确位置,则查找下一个
            index=index+1
    return index+1    

def main():
    A=[5,1,1,9,4,10]
    print(findFirstMissingInteger(A))

if __name__=="__main__":
    main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值