我的面试问题是这样的:

给定一个包含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.

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


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


当前回答

一些消除

一种方法是消除比特,但这实际上可能不会产生结果(很可能不会)。Psuedocode:

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

位计数

跟踪比特数;用最少的比特来产生一个值。同样,这也不能保证生成正确的值。

范围的逻辑

跟踪列表的顺序范围(按开始顺序)。范围由结构定义:

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

遍历文件中的每个值,并尝试将其从当前范围中删除。这个方法没有内存保证,但是它应该做得很好。

其他回答

他们可能想知道你是否听说过概率布鲁姆过滤器,它可以非常有效地确定一个值是否不属于一个大集合,(但只能确定它是集合的一个高概率成员)。

从文件中删除空白和非数字字符,并追加1。您的文件现在包含原始文件中没有列出的单个数字。

来自Reddit,作者:Carbonetc。

为了完整起见,这里有另一个非常简单的解决方案,它很可能需要很长时间才能运行,但只使用很少的内存。

设所有可能的整数为从int_min到int_max的范围,和 bool isNotInFile(integer)一个函数,如果文件不包含某个整数,则返回true,否则返回false(通过将该特定整数与文件中的每个整数进行比较)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}

对于1gb RAM的变体,您可以使用位向量。你需要分配40亿比特== 500 MB字节数组。对于从输入中读取的每个数字,将相应的位设置为“1”。一旦你完成了,遍历比特,找到第一个仍然是“0”的比特。它的索引就是答案。

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

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

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

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

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