【问题标题】:Find missing number in unsorted input with memory constraints在具有内存限制的未排序输入中查找缺失的数字
【发布时间】:2012-04-14 09:30:00
【问题描述】:

我正在阅读有关在Programming Pearls 中从一系列 40 亿个 32 位整数中查找缺失数字的问题,但无法完全找到解决方案。

给定一个最多包含 40 亿个 32 位的顺序文件 随机顺序的整数,找到一个不在文件中的 32 位整数。
约束:几百字节的主存,但充分利用 磁盘上的外部暂存文件

所描述的解决方案是一个过程,我们使用高位分隔范围中的数字(即,在第一遍中,我们将具有前导 0 位的数字写入一个文件,将具有前导 1 位的那些写入另一个文件。我们继续使用第二位等)并使用作为新搜索范围的一半作为新的搜索范围,其中包含少于该范围中数字的一半。

我在SO 中搜索并找到了一个类似的帖子,它并没有完全回答我的问题,即确切 数字是如何找到的(我不明白二进制搜索是如何适合单独的范围)。
@Damien_The_Unbeliever 的答案似乎最相关,但从我的角度来看,我认为按照这个过程我们最终会得到 2 个文件:一个范围内有 2 个数字的文件和一个有 1 个数字的文件。
通过将一个文件中的(一个)数字与其他文件中最大的数字相减,我们可以得到一个缺失的数字(不需要位掩码,我不太确定我们是否真的可以应用位掩码,因为范围在任何时候都是未知的)。

我错了吗?有人可以帮助解决这个问题吗?

【问题讨论】:

  • @MitchWheat:我已经链接到那个帖子,它没有回答我的问题。那个帖子是关于理解分离范围的过程。我的问题是如何找到实际数字。这个问题是除了我已经提到的Damien_The_Unbeliever 的提示之外,从未在该帖子中提及或回答
  • 您不需要任何外部存储。不用按位 k 分隔数字,只需分别计数即可。
  • @n.m. : 我不太清楚你的意思。你能详细说明一下吗?
  • @Li-aung Yip:请忽略我的评论,这种方法效率不高。

标签: java algorithm search binary-search


【解决方案1】:

您无需复制数据本身或将任何内容写入磁盘;只需计算数据的某些分区的成员即可识别开口。在传递次数和内存之间进行权衡(更多的内存允许更多的计数,更小的分区)。

这是一个 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 字节?

编辑:Li-aung Yip 的解决方案可扩展性更好,显然它可以修改为一次通过多个位进行分区。如果写入比读取慢,那么最好的解决方案可能是这个只读答案和 Li-aung Yip 基于磁盘的答案之间的混合。

按上述方法计算半字节,然后在计算第二组半字节时,根据第二半字节仅将匹配第一个半字节的数字(或可能只是它们的最后 28 位)写入 16 个文件.

选择您的第二个半字节并读取它以获取第三个半字节的计数,仅写入与第二个半字节匹配的数字等。如果文件接近容量,如果数字分布相当均匀,或者如果您选择每次最少完整的半字节,您必须写入不超过文件大小的 6.66% (1/16+1/16^2...)。

【讨论】:

  • 我无法遵循您的解决方案。尤其是您最终如何需要 64 字节数等。您能详细说明一下吗?
  • 当然。为方便起见,让我们以十六进制来讨论我们的整数,因此每个字符都是 4 位。在我们的第一遍中,我们计算有多少以 0、1、2、... e、f 开头的整数。那是 16 个不同的起始字符,所以我们需要 16 个整数或 64 个字节来一次计算它们。在剩下的 28 位中,每个起始字符最多可以有 2^28 种不同的组合;如果少于那个,我们知道有以该字符开头的数字不在我们的数据中,因此我们可以将搜索限制在该子集,依此类推。
【解决方案2】:

在将您的数字反复二进制分区成越来越小的文件后,您最终会得到:

  • 一堆包含两个数字的文件,它们仅在最后一个有效位上有所不同
  • 一个文件,里面只有一个数字。

通过翻转文件中数字的最后一位来获取丢失的数字。

以0x00到0x07的数字为例,缺少0x04:

000
001

010
011

... (missing)
101

110
111

101,翻转最低有效位,得到100,也就是缺少的0x04

【讨论】:

  • 注意:我假设只缺少一个整数。
  • +1。这更好地描述了我也是如何怀疑解决方案的。似乎位图在这里没用
  • 我看不出这会如何改变原来的问题。至少有 1 个缺失,这样我们仍然会得到一个。或者我错过了你的意思?
  • 如果缺少两个或多个数字,情况会更加复杂。如果上面的示例中都缺少010011 会发生什么? (这是一个微不足道的问题,它只是意味着你不能完全按照我描述的方式去做。)
  • 好吧,问题表明缺少的数字多于 1 个,如果该范围内恰好缺少 1 个数字,我不会打扰那个解决方案。我只需将所有数字相加并从中减去N(N+1)/2直接获取号码
【解决方案3】:

使用 32 位整数可以表示 40 亿个整数。将数字与自身进行 XOR 是在汇编代码中将寄存器清零的标准技巧。 如果您保证只有一个数字丢失,那么整数的按位异或就可以解决问题,这是一种 O(N) 解决方案,只需要一个额外的 32 位整数空间。考虑一个简单的例子,一个 3 位数字,因此数字 0-7 可表示,其中一个缺失。 假设 6 (110) 缺失 缺失 = n1 XOR n2 XOR n3 XOR .. XOR n7。 = 000 异或 001 异或 010 异或 011 异或 100 异或 101 异或 111

如果问题是找到 1 到 100 之间的缺失数字,您需要开始我对必须排除的元素进行异或运算。通过屏蔽数字中的位,可以使用 AND 从 32 位整数范围下降到更小的范围。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-07
    • 2021-05-25
    • 2020-04-06
    • 1970-01-01
    • 2013-11-30
    • 2012-07-08
    • 2016-12-14
    • 1970-01-01
    相关资源
    最近更新 更多