我正在阅读Programming Pearls
中从一系列40亿32位整数中找到缺失数字的问题,但无法得到解决方案。
给定一个包含最多40亿32位的顺序文件 以随机顺序排列整数,找到不在文件上的32位整数 约束:主内存的几个字节很多但是充分利用了 磁盘上的extrernal scratch文件
所描述的解决方案是一个过程,我们使用高位来区分范围中的数字(即第一遍中我们将带有前导0
位的数字写入一个文件而带有前导1
的数字我们继续使用第二位等)并使用新搜索范围分成两半,一半包含该范围内数字的一半。
我用Google搜索并在SO中发现了一个类似的帖子,这个帖子并没有完全回答我的问题如何找到确切的号(我不知道如何二分搜索适合于单独的范围)
@Damien_The_Unbeliever的答案似乎最相关,但从我的角度来看,我认为在这个过程之后我们最终会得到2个文件:一个文件,其中2个数字在范围内,一个文件包含1个数字。
通过用一个文件中最大的一个减去一个文件中的(一个)数字,我们可以得到一个丢失的数字(不需要位掩码,我不太确定我们是否可以实际应用一个位掩码,因为该范围在任何时候都是未知的)。
我错了吗?有人可以帮忙解决这个问题吗?
答案 0 :(得分:3)
您无需复制数据本身或将任何内容写入磁盘;只计算一些数据分区的成员来识别开口。权衡是在通过次数和内存之间(更多内存允许更多计数,更小的分区)。
这是8次通过的解决方案。我们一次使用4位对数据进行分区。 2 ^ 4 = 16个可能的值,因此我们需要64个字节来存储16个分区中每个分区的计数。因此每个4位半字节值都有一个相关的计数。
传递数据,递增与数字前四位中的半字节匹配的相关计数。如果分区已满,则其计数将为2 ^ 28。选择一个未满的半字节---这将是你结果的第一个半字节。
将您的计数归零并再次传递,忽略与第一个半字节不匹配的数字,并递增与数字中第二个半字节相关的计数。完整的第二个半字节将具有2 ^ 24的值。选择一个未满的。
以这种方式进行,直到你有所有8个半字节,并且你的答案是O(N)。
如果你一次只检查一个位,这将是一个二进制搜索需要32次传递。 (编辑:不是真正的二进制搜索,因为你仍然必须读取你正在跳过的值。这就是为什么它是O(N)。请参见下面的编辑。)如果你有一个KB的内存用于计数,你可以这样做4次通过;使用256 KB,你可以在2 ---实际上是128 KB,因为你可以使用短整数来计算你的数量。这里我们被限制在几百个字节---可能是6位/ 6个通道/ 256个字节?
如上所述进行通过计数半字节,然后在计算第二组半字节时,根据第二个半字节只将与第一个半字节匹配的数字(或可能只是它们的最后28位)写入16个文件。
选择你的第二个半字节并读取它以获得第三个半字节的计数,仅写入与第二个半字节匹配的数字等。如果文件接近容量,如果数字是相当均匀分布的,或者如果你选择了每次最不完整的半字节,你必须写不超过文件大小约6.66%(1/16 + 1/16 ^ 2 ...)。
答案 1 :(得分:2)
在将数字重复二进制分区为越来越小的文件后,您最终会得到:
通过翻转文件中数字的最后一位来获取缺失的数字。
以0x00到0x07之间的数字为例,缺少0x04:
000
001
010
011
... (missing)
101
110
111
选择101
,翻转最低位,然后获取100
,这是缺失的0x04
。
答案 2 :(得分:1)
使用32位整数表示4亿个整数。对自身进行异或运算是将汇编代码中的寄存器归零的标准技巧。 如果保证只有一个数字丢失,整数上的按位异或即可解决,这是一个O(N)解决方案,只需要一个额外的32位整数空间。考虑一个简单的例子,一个3位数,因此数字0-7可以表示,其中一个缺失。 假设6(110)丢失 missing = n1 XOR n2 XOR n3 XOR .. XOR n7。 = 000 XOR 001 XOR 010 XOR 011 XOR 100 XOR 101 XOR 111
如果问题是找到1到100之间的缺失数字,则需要从我的xoring开始必须排除的元素。通过屏蔽数字中的位,可以使用AND从32位整数范围下降到较小范围。