Skip to content

Instantly share code, notes, and snippets.

@sing1ee
Created September 10, 2013 15:53
Show Gist options
  • Save sing1ee/a0caadd7d3862e24a01f to your computer and use it in GitHub Desktop.
Save sing1ee/a0caadd7d3862e24a01f to your computer and use it in GitHub Desktop.

###缺失的数字分析

####原题 给定一个无序的整数数组,怎么找到第一个大于0,并且不在此数组的整数。比如[1,2,0] 返回 3, [3,4,-1,1] 返回 2。最好能O(1)空间和O(n)时间。 ####分析 首先数组是无序的,找到第一个大于0且不在数组中的元素,就是要找到大于0且不在数组中的最小的整数。要怎么处理呢?要找到最小的,不妨尝试从小到大排序,然后从1开始,查找是否在数组中,可以利用二分查找。这样整体的时间复杂度是O(nlogn),空间复杂度是O(1)。

离题目的要求,还差一些。如何改进呢?针对排序好的数组,我们做如下的观察:

  • 当缺失的数字为1的时候,A[0]<=0, A[1] != 1
  • 当缺失的数字为2的时候,A[0]<=0, A[1] = 1, A[2] != 2
  • 当缺失的数字为3的时候,A[0]<=0, A[1] = 1, A[2] = 2, A[3] != 3

通过上面的观察,可以发现,其实并不需要整个数组是有序的,只需要让数组中的0<A[i]<n的,能够还原到A[i]的位置,就像缺失3的时候,使得A[1] = 1, A[2] = 2,而且,这两个一定是这样的。后面的序就无需理会了。能够做到,不排序,就实现位置的还原么?且看如下的算法:

由后到前扫描数组:

  1. 如果A[i]<0 & A[i]>n,则continue;
  2. 如果A[i]=A[A[i]],则continue;
  3. 如果0<A[i]<n,那么可以还原,则swap(A[i], A[A[i]]),然后跳转到步骤2

然后,数组中如果存在0<A[i]<n的,已经保证A[i]=i了。此时:

  • 从1开始遍历,如果A[i]!=i,则i就是要找的数
  • 如果遍历完,没有找到,则说明i从[1,n-1],都有A[i]=i,那么这个时候就看A[0],如果A[0]是n,则返回n+1,如果A[0]不是n,则返回n,即可。

上面算法的时间复杂度是O(n)么?有的同学回想,在swap之后,仍然到第2步,可能还会出现交换,所以时间复杂度要高于O(n)。但实际上,swap之后,是会发生再次的交换,但,我们可以保证,每一次交换,都会使得一个数字还原,即A[i]=i,再一次交换,会让一个新的数字还原。假设数组中,可以还原的数字个数为k,则需要交换k次,k最多为n-1。这里要很小心,因为总的交换次数是k,并不是对于每一个元素,都会产生k次交换。如果是后者,总的时间复杂度就是O(kn)了,但因为总的交换次数是k,所以总的时间复杂度是O(n+k),就是O(n)。

【分析完毕】

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment