题目:
#面试题#给定一个无序的整数数组,怎么找到第一个大于0,并且不在此数组的最小整数。比如[1,2,0] 返回 3, [3,4,-1,1] 返回 2。最好能O(1)空间和O(n)时间。
解法:
直接考虑这个问题是比较困难的。不如先换个简单的问题。
问题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;
}
}