关于算法:找到一个不是40亿个给定值的整数

Find an integer not among four billion given ones

这是一个面试问题:

Given an input file with four billion integers, provide an algorithm to generate an integer which is not contained in the file. Assume you have 1 GB memory. Follow up with what you would do if you have only 10 MB of memory.

我的分析:

文件大小为4×109×4字节=16 GB。

我们可以进行外部排序,从而了解整数的范围。我的问题是,在已排序的大整数集中,检测缺少整数的最佳方法是什么?

我的理解(阅读所有答案后):

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

例1:我们有1 GB=1*109*8位=80亿位内存。解决方案:如果我们使用一个位来表示一个不同的整数,就足够了。我们没有需要排序。实施:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
        }
    }
}

案例2:10MB内存=10*106*8位=8000万位

1
2
3
4
5
6
7
8
9
10
11
12
13
Solution: For all possible 16-bit prefixes, there are 2^16 number of
integers = 65536, we need 2^16 * 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.

step1: Build the counter of each bucket through the first pass through the file.
step2: Scan the buckets, find the first one who has less than 65536 hit.
step3: Build new buckets whose high 16-bit prefixes are we found in step2
through second pass of the file
step4: Scan the buckets built in step3, find the first bucket which doesnt
have a hit.

The code is very similar to above one.

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

对迟到者的澄清:问题并不是说文件中只包含一个整数——至少大多数人不是这样解释的。不过,注释线程中的许多注释都是关于任务的变化的。不幸的是,将它引入评论线程的评论后来被作者删除了,所以现在看来,对它的孤立回复只是误解了一切。这很混乱。对不起的。


假设"integer"意味着32位:拥有10 MB的空间足以计算输入文件中具有任何给定16位前缀的数字数量,一次通过输入文件就可以计算出所有可能的16位前缀。至少有一个桶的撞击次数少于2^16次。再过一遍,找出那个桶里可能已经用过的数字。

如果它意味着超过32位,但仍有界大小:如上所述,忽略所有恰好位于(有符号或无符号;您的选择)32位范围之外的输入数字。

如果"integer"表示数学整数:请仔细阅读输入内容一次,并跟踪您见过的最长数字的长度。完成后,输出最大值加上一个一个有多个数字的随机数。(文件中的一个数字可能是需要超过10 MB才能准确表示的bignum,但如果输入是文件,则至少可以表示适合该文件的任何内容的长度)。


统计信息算法比确定性方法使用更少的过程来解决这个问题。

如果允许非常大的整数,那么可以生成一个在O(1)时间内可能是唯一的数字。一个像guid这样的伪随机128位整数只会与集合中现有的40亿个整数中的一个在每640亿个事例中发生碰撞,但不到一个。

如果整数限制为32位,则可以生成一个数字,该数字在单次传递中很可能是唯一的,使用的大小远小于10 MB。一个伪随机的32位整数与现有的40亿个整数中的一个碰撞的概率大约是93%(4e9/2^32)。1000个伪随机整数全部碰撞的概率小于120000亿分之一(一次碰撞的概率为1000)。因此,如果一个程序维护一个包含1000个伪随机候选对象的数据结构,并遍历已知的整数,消除候选对象的匹配项,那么几乎肯定会找到文件中没有的至少一个整数。


关于这个问题的详细讨论已在JonBentley"第1列"中进行了讨论。破解牡蛎"编程珍珠"Addison-Wesley第3-10页

Bentley讨论了几种方法,包括外部排序、使用多个外部文件的合并排序等,但Bentley建议的最佳方法是使用位域的单次传递算法,他幽默地称之为"Wonder Sort":)在这个问题上,40亿的数字可以用以下形式表示:

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

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

1
2
3
4
5
6
7
8
9
#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的算法对文件进行一次简单的传递,set在数组中放入适当的位,然后使用上面的test宏检查该数组以查找缺少的数字。

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

Hth.


由于问题没有指定我们必须找到文件中没有的最小可能数字,我们可以生成一个比输入文件本身长的数字。:)


对于1 GB RAM变体,您可以使用位向量。您需要分配40亿位=500 MB字节数组。对于从输入中读取的每个数字,将相应的位设置为"1"。完成后,重复这些位,找到第一个仍为"0"的位。它的索引就是答案。


如果它们是32位整数(可能从接近2^32的约40亿数字中选择),则您的40亿数字列表将最多占可能整数的93%(4*10^9/(2^32))。因此,如果您创建一个2^32位的位数组,每个位初始化为零(这将占用2^29字节~500 MB的RAM;记住一个字节=2^3位=8位),则读取整数列表,并为每个int将相应的位数组元素从0设置为1;然后读取位数组并返回第一个仍然为0的位。

如果您的RAM(约10 MB)较少,则需要稍微修改此解决方案。10 MB~83886080位仍然足以对0到83886079之间的所有数字进行位数组。因此,您可以在位数组中读取整数列表,并且只记录0到83886079之间的整数。如果这些数字是随机分布的,以压倒性的概率(100%的差异约为10^-2592069),您会发现一个丢失的int。事实上,如果您只选择数字1到2048(只有256字节的RAM),您仍然会发现丢失的数字占时间的绝大部分(99.99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 5%)。

但是,假设您没有大约40亿个数字;您有2^32-1个数字,RAM小于10 MB;所以任何一个小范围的int都有可能不包含这个数字。

如果您保证列表中的每个int都是唯一的,那么您可以将这些数字相加,然后将缺少一个的和减去完整的和(1/2)(2^32)(2^32-1)=9223372034707292160,以查找缺少的int。但是,如果一个int出现两次,则此方法将失败。

然而,你总是可以分而治之。一个简单的方法是读取数组并计算前半部分(0到2^31-1)和后半部分(2^31,2^32)中的数字。然后用更少的数字选择范围,然后重复将该范围分成两部分。(假设(2^31,2^32)中的数字少了两个,那么下一次搜索将计算范围(2^31,3*2^30-1),(3*2^30,2^32)中的数字。不断重复,直到你找到一个零数字的范围,然后你得到答案。应通过数组读取o(lg n)~32。

这种方法效率很低。我们在每个步骤中只使用两个整数(或者RAM的大约8个字节和一个4字节(32位)整数)。一个更好的方法是将其分为sqrt(2^32)=2^16=65536个箱,每个箱中有65536个数字。每个bin需要4个字节来存储其计数,因此需要2^18个字节=256 KB。因此,bin 0是(0到65535=2^16-1),bin 1是(2^16=65536到2*2^16-1=131071),bin 2是(2*2^16=131072到3*2^16-1=196607)。在python中,您可能会遇到如下情况:

1
2
3
4
5
6
7
import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

阅读大约40亿整数列表;计算2^16个存储箱中每个存储箱的整数,找到一个不完整的存储箱,该存储箱中没有全部65536个数字。然后你再次阅读40亿个整数列表;但这一次只注意到整数在这个范围内的时候;当你找到它们时,稍微翻转一下。

1
2
3
4
5
6
7
8
9
10
11
del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break


为什么让它如此复杂?你需要一个整数位目前在文件?

根据指定的规则,唯一使你需要到商店是最大的整数,你这么远,在文件中使用。整个文件已被读取一次,返回a的个数大于1。

没有任何风险或maxint击中,因为根据规则,没有限制的大小或数量的整数算法返回的位置。


我本可以在很小的空间使用二进制搜索的变体。

  • 点火开关允许的范围与数量,04294967295

  • ,中点。

  • 回路通过的文件号码是多少,计数小于或等于,高于价值的中点。

  • 如果没有数字是平等的,你做的。数的中点是答案。

  • 否则,选择范围是从一个fewest号码和重复步骤2与这新的范围。

  • 这将需要多达32通过一个线性扫描的文件,但它将只使用一个字节内存的储存范围和计数。

    这是一个基本的解决方案相同的AS -但它是使用双"16K。


    编辑好的,这并不是很仔细考虑,因为它假设文件中的整数遵循某种静态分布。显然他们不需要,但即使这样,也应该尝试一下:

    有大约43亿个32位整数。我们不知道它们是如何分布在文件中的,但最坏的情况是香农熵最高的情况:分布相等。在这种情况下,文件中没有出现任何一个整数的可能性是

    (232-1)/232)??????????????4。

    香农熵越低,平均概率越高,但即使在最坏的情况下,我们也有90%的机会在5次随机整数猜测后找到一个不发生的数字。只需使用伪随机生成器创建这些数字,并将它们存储在列表中。然后在int之后读取int并将其与所有猜测进行比较。有匹配项时,删除此列表项。在浏览完所有文件之后,很可能你还有不止一个猜测。使用它们中的任何一个。在极少的(10%甚至在最坏的情况下)没有猜测剩余的事件中,得到一组新的随机整数,这次可能更多(10->99%)。

    内存消耗:几十个字节,复杂度:O(N),开销:必须选择,因为大多数时间将花在不可避免的硬盘访问上,而不是比较整数。当我们不假设静态分布时,实际的最坏情况是每个整数最多出现一次,因为只有一次1-4000000000/232≈6%在所有整数中,不会出现在文件中。所以你需要更多的猜测,但这仍然不会消耗大量的记忆。


    如果你有一个整数,从2范围[ 0,1异或x ]然后他们只是在。例如:

    1
    2
    3
    4
    >>> 0 ^ 1 ^ 3
    2
    >>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
    5

    (我知道这不能回答所有的问题,但这是一个很好的答案一个非常类似的问题。)


    根据原始问题中的当前措辞,最简单的解决方案是:

    在文件中找到最大值,然后添加1。


    他们可以看看看你有听到这一概率布隆过滤器是非常有效的CAN if a值是不确定的一个大集的一部分,但只能在高概率的确定是一个成员的集合。)


    使用BitSet。40亿个整数(假设最多2^32个整数)以8个字节的速度压缩到一个位集中,即2^32/2^3=2^29=大约0.5GB。

    要添加更多的细节-每次读取一个数字时,在位集中设置相应的位。然后,对该位集进行传递,以查找不存在的第一个数字。事实上,您可以通过重复选择一个随机数并测试它是否存在来同样有效地做到这一点。

    实际上是位集。NextClearBit(0)将告诉您第一个非位集。

    看看位集API,它似乎只支持0..max_int,所以您可能需要2个位集—一个用于+有数字,一个用于-有数字—但是内存需求没有改变。


    如果没有大小的限制,单是把quickest文件长度,文件长度和生成的随机数(number of + 1或只是"11"……)。优势:你甚至不需要阅读文件,减少内存使用和可以近似为零。缺点:你想打印数十亿)位。

    然而,如果没有什么minimizing因子和内存使用情况,没有人是重要的,这将是最佳的解决方案。它甚至可能让你一个"最坏的滥用规则"奖。


    如果我们假设数字的范围总是2^n(2的偶数幂),那么exclusive或将起作用(如另一张海报所示)。至于原因,让我们证明一下:

    理论

    给定任何基于0的整数范围,其中缺少一个元素的2^n元素,您可以通过简单地将已知值异或在一起以产生缺少的数字来找到缺少的元素。

    证明

    我们来看看n=2。对于n=2,我们可以表示4个唯一的整数:0,1,2,3。它们有一点模式:

    • 0—00
    • 1—01
    • 2—10
    • 3—11

    现在,如果我们看,每一个位都被设置了两次。因此,因为它被设置为偶数次,而exclusive或of the numbers将产生0。如果缺少单个数字,则exclusive或将生成一个在exclusive或与缺少的数字一起使用时将导致0的数字。因此,丢失的数字和产生的独占或"或"数字完全相同。如果去掉2,得到的xor将是10(或2)。

    现在,让我们看看n+1。我们来调用每个位在nx中设置的次数,以及每个位在n+1y中设置的次数。y的值将等于y = x * 2,因为存在x个元素,n+1位设置为0,x个元素,n+1位设置为1。由于2x总是偶数,所以n+1总是将每个位设置偶数次。

    因此,由于n=2工作,n+1工作,xor方法将适用于n>=2的所有值。

    基于0的范围算法

    这很简单。它使用2*n位内存,因此对于任何小于等于32的范围,2个32位整数都可以工作(忽略文件描述符消耗的任何内存)。它只传递一次文件。

    1
    2
    3
    4
    5
    6
    long supplied = 0;
    long result = 0;
    while (supplied = read_int_from_file()) {
        result = result ^ supplied;
    }
    return result;

    任意基距离的算法

    该算法适用于任意起始数到任意终止数的范围,只要总范围等于2^n…这基本上将范围重新设为最小值0。但它需要2次传递文件(第一次获取最小值,第二次计算缺少的int)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    long supplied = 0;
    long result = 0;
    long offset = INT_MAX;
    while (supplied = read_int_from_file()) {
        if (supplied < offset) {
            offset = supplied;
        }
    }
    reset_file_pointer();
    while (supplied = read_int_from_file()) {
        result = result ^ (supplied - offset);
    }
    return result + offset;

    任意范围

    我们可以将这个修改后的方法应用于一组任意范围,因为所有范围都将至少跨越2^n的幂次。只有当有一个丢失的位时,这才有效。它需要对一个未排序的文件进行两次传递,但每次都会发现一个丢失的数字:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    long supplied = 0;
    long result = 0;
    long offset = INT_MAX;
    long n = 0;
    double temp;
    while (supplied = read_int_from_file()) {
        if (supplied < offset) {
            offset = supplied;
        }
    }
    reset_file_pointer();
    while (supplied = read_int_from_file()) {
        n++;
        result = result ^ (supplied - offset);
    }
    // We need to increment n one value so that we take care of the missing
    // int value
    n++
    while (n == 1 || 0 != (n & (n - 1))) {
        result = result ^ (n++);
    }
    return result + offset;

    基本上,将范围重新设为0左右。然后,在计算exclusive或时,它计算要附加的未排序值的数量。然后,它在未排序的值的计数上加1以处理丢失的值(对丢失的值进行计数)。然后,继续XORing n值,每次递增1,直到n是2的幂。然后将结果重新基于原始基础。完成。

    下面是我在PHP中测试的算法(使用数组而不是文件,但概念相同):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function find($array) {
        $offset = min($array);
        $n = 0;
        $result = 0;
        foreach ($array as $value) {
            $result = $result ^ ($value - $offset);
            $n++;
        }
        $n++; // This takes care of the missing value
        while ($n == 1 || 0 != ($n & ($n - 1))) {
            $result = $result ^ ($n++);
        }
        return $result + $offset;
    }

    在一个数组中输入任意范围的值(我测试了包括负值),其中一个值在该范围内,但却丢失了,每次都会找到正确的值。

    另一种方法

    既然我们可以使用外部排序,为什么不检查一个间隙呢?如果我们假设文件是在运行此算法之前排序的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    long supplied = 0;
    long last = read_int_from_file();
    while (supplied = read_int_from_file()) {
        if (supplied != last + 1) {
            return last + 1;
        }
        last = supplied;
    }
    // The range is contiguous, so what do we do here?  Let's return last + 1:
    return last + 1;


    检查文件的大小的输入,然后输出任何数太大,这是由一个代表文件的大小。这可能似乎像一个廉价的把戏,但它是一个创造性的解决方案一个面试问题,它neatly sidesteps记忆的问题,它的技术上的O(n)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void maxNum(ulong filesize)
    {
        ulong bitcount = filesize * 8; //number of bits in file

        for (ulong i = 0; i < bitcount; i++)
        {
            Console.Write(9);
        }
    }

    要打印bitcount 1 10,这将永远是大于2 bitcount。技术上,你要拍数是2 bitcount(4×109~1),因为你知道有(4到1的整数)在其他文件的压缩与完美,即使他们会取至少每一个比特。


    • 最简单的方法是找到文件中的最小数字,并返回小于该数字的1。它使用O(1)存储,对n个数字的文件使用O(n)时间。但是,如果数字范围有限,它将失败,这可能使min-1不是a-数字。

    • 已经提到了使用位图的简单而直接的方法。该方法使用O(N)时间和存储。

    • 还提到了一种2^16计数桶的二次通过法。它读取2*n整数,因此使用O(n)时间和O(1)存储,但它不能处理超过2^16个数字的数据集。但是,通过运行4次而不是2次,它很容易扩展到(例如)2^60 64位整数,并且通过只使用内存中尽可能多的存储箱并相应地增加传递次数,很容易适应使用小内存,在这种情况下,运行时间不再是O(n),而是O(n*logn)。

    • 正如LTN100所指出的,Rfrankel和IrcMaxell所提到的将所有数字相加的方法,最终回答了StackOverflow中提出的问题。它使用O(1)存储和O(n)运行时。如果我们假设32位整数,xor有7%的概率产生一个不同的数字。理由:考虑到大约4G个不同的数字xor'd在一起,而大约300m不在文件中,每个位位置的设置位数有相等的机会是奇数或偶数。因此,2^32个数字产生XOR结果的可能性相等,其中93%已在文件中。注意,如果文件中的数字不都是不同的,那么XOR方法的成功概率会增加。


    技巧问题,除非引证不当。只需读取文件一次,就可以得到最大整数n,并返回n+1

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


    出于某种原因,我一读到这个问题,就想到了对角化。我假设任意大整数。

    读第一个数字。用零位填充,直到有40亿位。如果第一个(高阶)位为0,则输出1;否则输出0。(你不必离开键盘:如果数字中没有足够的位,你只需要输出一个1。)对第二个数字做同样的操作,除了使用第二个位。以这种方式继续浏览文件。您将一次输出一个40亿位的数字,这个数字与文件中的任何数字都不相同。证据:它和第n个数相同,然后他们会同意第n个位,但不是通过构造。


    Strip the white space and non numeric characters from the file and append 1. Your file now contains a single number not listed in the original file.

    从carbonetc Reddit模式。P></


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

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

    假设每个整数都是32位的,那么如果位标记完成了,它们将方便地放入1GB的RAM中。


    只是for the is another of完备的缘故,这里的解决方案很简单,which will take a long time Most likely to Run甚甚小,但uses记忆。P></

    在可能的范围内,好让integers from the int_minto int_max,and在函数返回True,如果bool isNotInFile(integer)which does not contain the文件一定integer和其他虚假(by that with each integer整数?比较一定在文件)P></

    1
    2
    3
    4
    5
    6
    for (integer i = int_min; i <= int_max; ++i)
    {
        if (isNotInFile(i)) {
            return i;
        }
    }


    我想回答1: ;GB版

    没有足够的信息的问题,所以我想一些假设第一状态:

    一个32位的整数范围是-2147483648到2147483647。

    伪代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

    foreach (var number in file) {
        bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
    }

    for (var i = 0; i < 4294967296; i++) {
        if (bitArray[i] == 0) {
            return i - 2147483648;
        }
    }

    对于10 MB内存限制:

  • 将数字转换为其二进制表示形式。
  • 创建一个二进制树,其中LEFT=0和RIGHT=1。
  • 使用其二进制表示法在树中插入每个数字。
  • 如果已经插入了数字,则已经创建了叶。
  • 完成后,只需使用以前未创建的路径来创建请求的编号。

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

    编辑

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

    编辑二

    也不需要完全地建造这棵树。如果数字相似,您只需要构建深分支。如果我们也切断分支,那么这个解决方案实际上可能有效。


    只要我们做创造性的回答,这里就有另一个。

    使用外部排序程序对输入文件进行数字排序。这将适用于您可能拥有的任何内存量(如果需要,它将使用文件存储)。读取经过排序的文件并输出丢失的第一个数字。


    正如Ryan所说的,对文件进行排序,然后遍历整数,当跳过某个值时,就会得到它:)

    向下投票编辑:操作人员提到文件可以排序,所以这是一个有效的方法。


    位消除

    一种方法是消除位,但这可能实际上不会产生结果(可能不会)。伪代码:

    1
    2
    3
    4
    5
    6
    long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
    foreach long fileVal in file
    {
        val = val & ~fileVal;
        if (val == 0) error;
    }

    位计数

    跟踪位计数;并使用最小数量的位来生成值。同样,这不能保证生成正确的值。

    范围逻辑

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

    1
    2
    3
    4
    5
    struct Range
    {
      long Start, End; // Inclusive.
    }
    Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

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


    2128 1018(which is+1×16×1018(28)+ 1)-它不能在通用答案for today?This represents a number that cannot be举行16 EB的文件的最大文件大小,which is the current在任何文件系统。P></


    我认为这是solved问题(见上述),但也有兴趣在我心灵的家园to keep端EN might get要:P></

    确切的说因为是if there are 4294967295(2 ^ 32 32位integers - 1)与不repeats missing is only one,and therefore,there is a简单的解决方案。P></

    在启动和运行的全零,for each integer在add that with the文件(32位整数溢出effectively,runningtotal = 4294967296(runningtotal + nextinteger)%)。2盎司完整4294967296,add to the running /总,再与32位溢出。subtract this from 4294967296,and the result is the missing integer。P></

    "missing the only one is integer"问题的唯一可解与一个本垒打,只和64位数据RAM银行(32 dedicated to the running for the total to the next 32读,在integer)。P></

    通用规范:corollary is the更多的比赛是非常简单的if we t to how many位关注与the result must have integer。我们只是在产生足够大的整数,它cannot be given the文件包含在我们的国王。这一次,以检查绝对最小的RAM。see the伪。P></

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Grab the file size
    fseek(fp, 0L, SEEK_END);
    sz = ftell(fp);
    # Print a '2' for every bit of the file.
    for (c=0; c<sz; c++) {
      for (b=0; b<4; b++) {
        print"2";
      }
    }


    如果你不想在32位的约束,只是返回一随机数生成(64位或128位的,如果你是一个悲观主义者)。碰撞的机会是1 in 2^64/(4*10^9) = 4611686018.4(约1 4万亿)。你会对我们的时间!

    (joking…孩子。)


    您不需要对它们进行排序,只需要重复地对它们的子集进行分区。

    第一步就像流沙的第一步。选择其中一个整数x,并使用它对数组进行传递,将所有小于x的值放在其左侧,将大于x的值放在其右侧。查找X的哪一侧具有最大数量的可用插槽(列表中没有整数)。这很容易通过比较x的值和它的位置来计算。然后在x的那一侧的子列表上重复分区。然后用最大的可用整数等重复子列表上的分区。比较的总数应该是大约40亿,给或取。


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

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

    这比标记位慢得多,因此如果您可以将树的较低级别存储在文件中,那么它只能是10MB情况的解决方案。

    存储这样一棵树的一种方法是使用范围的开始作为键,范围的结束作为值的B-树。最坏的情况是,当您得到所有奇数或偶数整数时,这意味着为树存储2^31值或数十GB…哎哟。最佳情况是一个经过排序的文件,在该文件中,整棵树只能使用几个整数。

    所以不是正确的答案,但我想我会提到这种方式。我想我会不通过面试的;—)


    也许我完全缺失这个问题点,但你想找到丢失的文件从一个排序的整数到整数?

    嗯……真的吗?让我们想一想是什么样子:搜索文件

    1 2 3 4 5 6…一个第一……等。

    到这个问题的解决方案似乎是微不足道的。


    Given an input file with four billion integers, provide an algorithm
    to generate an integer which is not contained in the file. Assume you
    have 1 GiB memory. Follow up with what you would do if you have only
    10 MiB of memory.

    文件大小为4*109*4字节=16 GiB

    < /块引用>

    对于32位无符号整数

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

    我建议的解决方案:没有错误检查的C++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #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;
    }

    复杂性

    1
    2
    3
    4
    5
    Space ~ 2^32 bits = 2^29 Bytes = 2^19 KB = 2^9 MB = 1/2 GB

    Time ~ Single Pass

    Completeness ~ Yes


    我可能太仔细阅读此说,但问题是在不生成的整数中包含的文件。i只是排序的列表和添加1个最大的入口。BAM的整数是在逆境中包含的文件。


    老问题,但我想知道"非功能性"需求。在我看来,应该给出一个线索——如果这个问题是在一本书以外的其他地方提出的,然后继续讨论所有的可能性和利弊。在面试中经常会出现这样的问题,这让我感到困惑,因为在不了解软性要求的情况下,无法给出明确的答案,即"查找缺失的数字必须非常快,因为它在一秒钟内使用了x次"。

    我认为这样的问题也许可以给出一个合理的答案。

    • 我会将所有的数字合并到一个新的文件中,每int使用4个字节。当然,这在开始时会很慢。但是它可以用很小的内存量完成(不需要将所有内存都保存在RAM中)
    • 使用二进制搜索检查预排序文件中是否存在数字。因为我们每个值保留4个字节,所以这没问题。

    缺点:

    • 文件大小
    • 慢第一种-但只需要一次

    优势:

    • 查找速度非常快

    所以,对于一本书来说,这是一个非常好的问题。但我认为,当要求一个最佳解决方案时,这是一个奇怪的问题,当要解决的问题还不完全清楚时。


    我想出了下面的算法。

    我的想法是:对整数的所有文件进行一次检查,对每个位的位置计数0和1。0和1的数量必须是2^(numOfBits)/2,因此,如果数量小于预期值,我们可以使用结果的数量。

    例如,假设整数是32位,那么我们需要

    1
    2
    int[] ones = new int[32];
    int[] zeroes = new int[32];

    对于每个数字,我们必须迭代32位并增加0或1的值:

    1
    2
    3
    4
    for(int i = 0; i < 32; i++){
       ones[i] += (val>>i&0x1);
       zeroes[i] += (val>>i&0x1)==1?0:1;
    }

    最后,在处理文件之后:

    1
    2
    3
    4
    5
    int res = 0;
    for(int i = 0; i < 32; i++){
       if(ones[i] < (long)1<<31)res|=1<<i;
    }
    return res;

    注意:在某些语言(Ex.java)中,1<31是负数,因此,(long)1<31是正确的方法。


    当然,在有限的经验(刚刚开始学习Java在UNI)的情况下,您可以运行TrHOUE一套(桶)的INT,如果没有找到桶的处理。这将释放空间并对每个数据单元进行检查。如果找到要查找的内容,请将其添加到计数变量中。可能需要很长时间,但是,如果为每个节生成多个变量,并对每个变量运行检查计数,并确保它们同时退出/处理,那么变量存储不应增加?并将加快检查过程。只是一个想法。