我的面试问题是这样的:

给定一个包含40亿个整数的输入文件,提供一种算法来生成一个文件中不包含的整数。假设您有1gb内存。如果你只有10mb的内存,你会怎么做。

我的分析:

文件大小为4×109×4 bytes = 16gb。

我们可以进行外部排序,从而知道整数的范围。

我的问题是,在已排序的大整数集中检测缺失整数的最佳方法是什么?

我的理解(看完所有答案后):

假设我们讨论的是32位整数,有232 = 4*109个不同的整数。

情况1:我们有1gb = 1 * 109 * 8位= 80亿位内存。

解决方案:

如果我们用一位表示一个不同的整数,这就足够了。我们不需要排序。

实现:

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

情形二:10mb内存= 10 * 106 * 8bits = 8000万bits

Solution: For all possible 16-bit prefixes, there are 216 number of integers = 65536, we need 216 * 4 * 8 = 2 million bits. We need build 65536 buckets. For each bucket, we need 4 bytes holding all possibilities because the worst case is all the 4 billion integers belong to the same bucket. Build the counter of each bucket through the first pass through the file. Scan the buckets, find the first one who has less than 65536 hit. Build new buckets whose high 16-bit prefixes are we found in step2 through second pass of the file Scan the buckets built in step3, find the first bucket which doesnt have a hit. The code is very similar to above one.

结论: 我们通过增加文件传递来减少内存。


为那些迟到的人澄清一下:这个问题,正如所问的,并不是说有一个整数没有包含在文件中——至少大多数人不是这么理解的。不过,评论线程中的许多评论都是关于任务的变化。不幸的是,把它引入评论线程的评论后来被它的作者删除了,所以现在看起来它的孤儿回复只是误解了一切。这很让人困惑,抱歉。


当前回答

假设“整数”表示32位:10mb的空间足以让您计算输入文件中有多少个数字,具有任何给定的16位前缀,对于所有可能的16位前缀,在一次通过输入文件。至少有一个桶被击中的次数少于216次。执行第二次传递,以查找该bucket中哪些可能的数字已经被使用。

如果它意味着超过32位,但仍然是有限的大小:执行上述操作,忽略所有恰巧落在(有符号或无符号;32位范围。

如果“integer”指的是数学整数:通读输入一次,记录你见过的最长数字中最大的数字长度。当你完成后,输出最大值加1是一个多一位的随机数。(文件中的一个数字可能是一个大于10mb的大字节,但如果输入是一个文件,那么您至少可以表示任何适合它的长度)。

其他回答

通过在某种树结构中存储未访问的整数范围,可以在读取现有整数后加快查找丢失的整数的速度。

首先存储[0..]4294967295],每次读取一个整数,你拼接它所在的范围,当它变成空的时候删除一个范围。最后,你得到了在范围内缺少的精确的整数集。所以如果你把5作为第一个整数,你会得到[0..4]和[6..4294967295]。

这比标记位要慢得多,所以它只适用于10MB的情况,前提是你可以将树的较低级别存储在文件中。

存储这种树的一种方法是使用b -树,其范围的开始作为键,范围的结束作为值。最坏的情况是当你得到的都是奇数或偶数时,这意味着要为树存储2^31个值或几十GB……哎哟。最好的情况是一个排序文件,其中您只需要为整个树使用几个整数。

所以这并不是正确的答案,但我想我应该提到这种方法。我想我面试不及格;-)

假设“整数”表示32位:10mb的空间足以让您计算输入文件中有多少个数字,具有任何给定的16位前缀,对于所有可能的16位前缀,在一次通过输入文件。至少有一个桶被击中的次数少于216次。执行第二次传递,以查找该bucket中哪些可能的数字已经被使用。

如果它意味着超过32位,但仍然是有限的大小:执行上述操作,忽略所有恰巧落在(有符号或无符号;32位范围。

如果“integer”指的是数学整数:通读输入一次,记录你见过的最长数字中最大的数字长度。当你完成后,输出最大值加1是一个多一位的随机数。(文件中的一个数字可能是一个大于10mb的大字节,但如果输入是一个文件,那么您至少可以表示任何适合它的长度)。

您可以使用位标志来标记一个整数是否存在。

遍历整个文件后,扫描每个位以确定数字是否存在。

假设每个整数是32位,如果进行了位标记,它们将方便地放入1gb RAM中。

既然我们在做创造性的回答,下面是另一个问题。

使用外部排序程序对输入文件进行数字排序。这将适用于任何数量的内存(如果需要,它将使用文件存储)。 通读排序文件并输出缺少的第一个数字。

好的,这并没有经过充分的思考,因为它假设文件中的整数遵循某种静态分布。显然他们不需要这样做,但即使这样,也应该试试这个:


有≈43亿个32位整数。我们不知道它们在文件中是如何分布的,但最糟糕的情况是具有最高香农熵的情况:均匀分布。在这种情况下,任何一个整数不出现在文件中的概率为

((2³²-1)/2³²)⁰⁰⁰⁰≈.4

The lower the Shannon entropy, the higher this probability gets on the average, but even for this worst case we have a chance of 90% to find a nonoccurring number after 5 guesses with random integers. Just create such numbers with a pseudorandom generator, store them in a list. Then read int after int and compare it to all of your guesses. When there's a match, remove this list entry. After having been through all of the file, chances are you will have more than one guess left. Use any of them. In the rare (10% even at worst case) event of no guess remaining, get a new set of random integers, perhaps more this time (10->99%).

内存消耗:几十个字节,复杂度:O(n),开销:neclectable,因为大部分时间将花费在不可避免的硬盘访问上,而不是比较int类型。 当我们不假设静态分布时,实际最坏的情况是每个整数都出现最大值。曾经,因为那时只有 1 - 4000000000/2³²≈6% 所有的整数都不会出现在文件中。因此,您需要更多的猜测,但这仍然不会消耗大量的内存。