我的面试问题是这样的:

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

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


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


当前回答

这是个陷阱问题,除非引用不当。只需要通读文件一次,得到最大整数n,并返回n+1。

当然,您需要一个备份计划,以防n+1导致整数溢出。

其他回答

为什么要把它弄得这么复杂?你要的是一个文件中没有的整数?

根据指定的规则,您唯一需要存储的是到目前为止在文件中遇到的最大整数。读取整个文件后,返回一个大于1的数字。

不存在触及maxint或任何东西的风险,因为根据规则,对算法返回的整数或数字的大小没有限制。

一些消除

一种方法是消除比特,但这实际上可能不会产生结果(很可能不会)。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 };

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

对于10mb内存限制:

将数字转换为二进制表示形式。 创建一个二叉树,其中左= 0,右= 1。 使用二进制表示将每个数字插入树中。 如果已经插入了一个数字,则叶子将已经创建。

完成后,只需使用之前未创建的路径来创建所请求的数字。

40亿数字= 2^32,这意味着10 MB可能不够。

EDIT

优化是可能的,如果已经创建了两个末端叶并且有一个共同的父级,那么可以将它们删除,并且父级标记为不是解决方案。这减少了分支,减少了对内存的需求。

编辑II

没有必要完全构建树。只有在数字相似的情况下才需要构建深度分支。如果我们也砍掉树枝,那么这个解决方案实际上可能有效。

给定一个包含40亿个整数的输入文件,提供一个算法 生成文件中不包含的整数。假设你 有1gib的内存。接着问如果只有你会怎么做 10内存MiB。 文件大小为4 * 109 * 4字节= 16gib

如果是32位无符号整数

0 <= Number < 2^32
0 <= Number < 4,294,967,296

我建议的解决方案是:c++不进行错误检查

#include <vector>
#include <fstream>
#include <iostream>
using namespace std;

int main ()
{
    const long SIZE = 1L << 32;

    std::vector<bool> checker(SIZE, false);

    std::ifstream infile("file.txt");  // TODO: error checking

    unsigned int num = 0;

    while (infile >> num)
    {
        checker[num] = true ;
    }

    infile.close();

    // print missing numbers

    for (long i = 0; i < SIZE; i++)
    {
        if (!checker[i])
            cout << i << endl ;
    }

    return 0;
}

复杂性

Space ~ 232 bits = 229 Bytes = 219 KB = 29 MB = 1/2 GB 时间~单次通过 完整性~是

关于这个问题的详细讨论已经在Jon Bentley的“第一栏”中讨论过。“编程珍珠”Addison-Wesley第3-10页

Bentley讨论了几种方法,包括外部排序,使用几个外部文件的归并排序等,但Bentley建议的最佳方法是使用位字段的单次传递算法,他幽默地称之为“神奇排序”:) 来看看这个问题,40亿个数字可以表示为:

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

实现bitset的代码很简单:(取自解决方案页面)

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

Bentley的算法只对文件进行一次传递,在数组中设置适当的位,然后使用上面的测试宏检查这个数组以找到缺失的数字。

如果可用内存小于0.466 GB, Bentley建议使用k-pass算法,根据可用内存将输入划分为不同的范围。举一个非常简单的例子,如果只有1个字节(即处理8个数字的内存)可用,并且范围从0到31,我们将其分为0到7、8-15、16-22等范围,并在每次32/8 = 4次传递中处理这个范围。

HTH.