面试题整理-最小没出现的整数

题目

#面试题#给定一个无序的整数数组,怎么找到第一个大于0,并且不在此数组的最小整数。比如[1,2,0] 返回 3, [3,4,-1,1] 返回 2。最好能O(1)空间和O(n)时间。

来源http://weibo.com/lirenchen

 

解法:

直接考虑这个问题是比较困难的。不如先换个简单的问题。

 

问题1

给定一个数组,长度为n,除a[0]以外,其他元素都是a[i] == i。那么请找出第一个大于0,且不在此数组中的最小整数。

答案就是:  n + (a[0] == n);

 

问题2

给定一个数组,长度为n,某几个位置的元素满足 a[x] < 0 或者 a[x] > n,余下的元素都满足a[i] == i。那么请找出第一个大于0,且不在此数组中的最小整数。

答:

很明显:当x的值限定为0时。就变成了问题1。

除此之外,这个问题的解也容易求得。

情况1: 首先从1~n开始扫描,当发现i != a[i]时,直接返回i。此时的i必定是最小的未出现的整数。

情况2: 当扫描完1~n之后,那么就回归至问题1了。

for (i = 1; i < n; ++i) {
    if (i != a[i]) return i;
}
return (n + (n == a[0]));


问题3

当给出问题2之后,就需要考虑如何把原题目转换成为问题2了。

实际上要完成的任务就变成了,如何把一个数组中的元素元神归位。也就是让a[i] == i。如果能让这些元素元神归位,那么就转换成为问题2了。就很容易求解了。

算法如下:

       我们从后往前扫描,i = n - 1 to 0;

                  step1如果发现a[i] < 0 || a[i] > n; 则 continue;

                  step2 如果发现a[i] == a[a[i]]; 则continue;

                  step3 如果发现0 < a[i] < n

                          说明需要将a[i]元神归位。也就是放到a[a[i]]上去。

                           swap(a[i], a[a[i]]).

                           再跳转至step2。

 

Note: 需要注意一种有重复数的情况,比如a[11] = 2, a[2] = 2。这时候,就不用进行交换了。直接处理下一个元素。

可以给出代码:

    int i = n, t, temp;
    if (!a || n <= 0) return -1;

    while ((--i) >= 0) {
        while (0 < a[i] && a[i] < n && i != a[i]) {
            t = a[i];
            if (a[i] == a[t]) break;
            temp = a[i];
            a[i] = a[t];
            a[t] = temp;
        }
    }


好吧,到现在为止,原题已经变得很简单了。可以直接给出代码了。

原题解答

int find(int *a, int n) {
    int i = n, t, temp;
    if (!a || n <= 0) return -1;

    while ((--i) >= 0) {
        while (0 < a[i] && a[i] < n && i != a[i]) {
            t = a[i];
            if (a[i] == a[t]) break;
            temp = a[i];
            a[i] = a[t];
            a[t] = temp;
        }
    }

    for (i = 1; i < n; ++i)
        if (a[i] != i) return i;

    return (n + (a[0] == n));
}


算法复杂度分析

这里再加上对算法复杂度的分析。算法的复杂度在下面这段代码看起来。唔~~很难说清楚到底复杂底是多少。

我们不妨回归到问题本身。把一些条件分析清楚:

条件1、对于给定的一个数组。元神归位数目是有限的。假定为k。这个k表示的是,当处理完成之后,a[i] == i的数目。

             k的范围是固定的: 0 <= k < n。

条件2、对于每个元素而言,while (0 < a[i] && a[i] < n && i != a[i]) 这个while 循环里面,每交换一次,就会使得一个元神归位。如果交换了xi次,就会使得xi个元神归位。

             这个是很容易搞清楚的。因为每次交换的效果,都是让某个元神归了位。

             比如a[11] = 5, a[5] = 2, a[2] = 0;

             第一次交换: a[11] = 2, a[5] = 5, a[2] = 0;

             第二次交换: a[11] = 0, a[5] = 5, a[2] = 2;

条件3、对于整个数组而言,元神归位的数目K应该是满足:

             x0 + x1 + x2 + x3 + ..... + xn-1 = K

             这也就是说交换K次。

             注意啊: 不是每个i都会交换K次。而是所有的i交换次数的总和,应该是K次。

 

可以想象一种极端的情况,比如,当i=n-1的时候,置换次数最多。刚好把所有的元神都归位了。那么从i = n - 2开始,就再也不会进入到里面的while循环中去了。

再例如:如果i=n-1的时候,使得2个元神归了位。那么从 i = n-2 ~ 0,则只需要让k-2个元神归位了。因为已经归位的,是不用再去处理的。

 

想到这里,你应该明白了,下面这段代码的复杂度是O(n) + O(K)。由于0<=K < n。所以复杂度是O(n)。也就是线性时间完成了任务

 

    while ((--i) >= 0) {
        while (0 < a[i] && a[i] < n && i != a[i]) {
            t = a[i];
            if (a[i] == a[t]) break;
            temp = a[i];
            a[i] = a[t];
            a[t] = temp;
        }
    }


 

 

 

 

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值