腾讯高频面试题——40亿QQ号,1G内存,怎么去重?
相信不少人在网上都刷到过这个面试题,“40亿QQ号,1G内存,怎么去重??”。 ——这是一个非常典型的海量数据处理问题。
特别是在海量数据处理的场景中,去重是一个非常常见且核心的问题。
面试中,这类问题往往以“在有限内存下处理超大数据量”的形式出现,就是在考察面试者对数据结构、算法、内存管理以及系统设计的综合能力。
这个问题不仅考验对基础知识的掌握,更要求你能在资源受限的条件下,设计出高效且可行的解决方案。
今天就带大家一起从问题分析入手,逐步探讨各种去重方案,从最直观的方法到更高级的位图、布隆过滤器,再到分治策略,详细分析它们的原理、优缺点以及内存消耗,最终给出在1G内存限制下的最佳方案。
1、问题分析与约束条件
首先,我们来仔细分析一下这个面试题的几个关键信息和隐含约束:
- 数据量:40亿个QQ号。QQ号通常是数字,假设为
uint32_t类型(最大值约42亿),或者uint64_t类型(如果QQ号位数较长)。为了简化计算,假设QQ号是32位无符号整数,即范围在0到4,294,967,295之间。 - 内存限制:1GB(1024MB)可用内存。这是最核心的约束,意味着我们不能将所有QQ号直接加载到内存中进行处理。
- 目标:去重。即找出所有不重复的QQ号。
- 隐含条件:
- 数据来源:QQ号可能存储在一个大文件中,或者通过网络流式传输。通常假设数据是静态的,存储在一个文件中。
- 处理时间:如果没有明确说明,但在面试中,通常要求解决方案在合理的时间内完成,不能是无限期的。
- 精确性要求:是否允许一定的误判率?这会影响我们选择的数据结构。
1.1、QQ号的数据特性
如果QQ号是32位无符号整数,那么每个QQ号占用4字节。40亿个QQ号的总大小为:
40亿 * 4 字节 = 160亿 字节 = 16 GB
显然,16GB的数据量远超1GB的内存限制,因此我们无法将所有QQ号一次性加载到内存中进行处理。这要求我们采用外部算法或内存效率更高的数据结构。
1.2、内存限制的挑战
1GB内存意味着我们只能存储大约 1GB / 4字节/QQ号 = 250MB / 4字节/QQ号 = 2.5亿 个QQ号。这进一步强调了我们需要寻找一种不直接存储QQ号本身,或者以极高效率存储QQ号存在性的方法。
接下来,将基于这些约束条件,逐步探讨各种去重方案。
2、基础去重方法及其局限性
在讨论高效解决方案之前,我们先回顾一些常见的去重方法,并分析它们在“40亿QQ号,1G内存”这个场景下的局限性。
2.1、使用哈希表(Hash Table / std::unordered_set)
原理:
哈希表是一种非常高效的数据结构,可以实现平均O(1)的插入和查找操作。我们可以遍历所有QQ号,将每个QQ号插入到一个哈希表中。在插入之前,先检查哈希表中是否已存在该QQ号,如果存在则说明是重复的,否则插入。最终哈希表中存储的就是所有不重复的QQ号。
内存消耗分析:
假设每个QQ号是32位无符号整数(4字节)。
一个哈希表除了存储数据本身,还需要额外的开销:
- 哈希表节点开销:每个节点通常包含数据本身、指向下一个节点的指针(解决哈希冲突,如链地址法)、以及一些管理信息。假设每个节点额外开销为8字节(一个指针)+ 4字节(数据本身) = 12字节。
- 哈希表本身开销:哈希表通常需要维护一个数组,其大小通常是元素数量的1.5到2倍,以保证较低的冲突率。
假设有N个不重复的QQ号,最坏情况下N接近40亿。那么内存消耗将是:
N * (4字节(QQ号) + 额外开销)
即使我们假设每个QQ号的额外开销只有4字节(非常乐观,实际可能更高),那么存储40亿个不重复QQ号所需的内存为:
40亿 * (4 + 4) 字节 = 40亿 * 8 字节 = 32 GB
这远超1GB的内存限制。即使QQ号数量远小于40亿(例如,只有2.5亿个不重复QQ号),2.5亿 * 8 字节 = 2 GB,仍然超出了1GB的限制。
局限性:
- 内存溢出:对于40亿这种量级的数据,哈希表需要巨大的内存空间来存储所有不重复的QQ号及其管理信息,这在1GB内存限制下是不可行的。
- 性能问题:当哈希表过大时,哈希冲突会增加,导致查找和插入性能下降。同时,内存访问模式可能不友好,影响缓存效率。
2.2、使用std::set或std::map(基于红黑树)
原理:
std::set和std::map在C++中通常基于红黑树实现。它们可以自动维护元素的有序性,并在插入时自动去重。插入和查找的平均时间复杂度为O(logN)。
内存消耗分析:
红黑树的每个节点除了存储数据本身,还需要存储:
- 数据本身:4字节(QQ号)。
- 颜色信息:通常1位,可以忽略不计。
- 父节点、左子节点、右子节点指针:每个指针在64位系统上占用8字节,共24字节。
因此,每个节点至少需要 4字节(QQ号) + 24字节(指针) = 28字节。如果考虑节点对齐和额外开销,实际可能更大。
存储40亿个不重复QQ号所需的内存为:
40亿 * 28 字节 = 112 GB
这比哈希表所需的内存还要大得多。
局限性:
- 内存溢出:与哈希表类似,红黑树的节点开销更大,内存消耗更为严重,远超1GB限制。
- 性能问题:虽然
logN的复杂度在理论上比哈希表的O(1)差,但在实际操作中,红黑树的内存访问模式可能比哈希表更连续,但在数据量巨大时,其性能瓶颈依然是内存访问和比较操作。
2.3、排序后去重
原理:
如果数据可以完全加载到内存中,我们可以先对所有QQ号进行排序,然后遍历排序后的序列,相邻元素相同的即为重复。这种方法的时间复杂度通常为O(N log N)(排序),空间复杂度为O(N)(存储所有数据)。
内存消耗分析:
如前所述,40亿个QQ号需要16GB的存储空间。即使是原地排序算法,也需要至少16GB的内存来存储所有QQ号。
局限性:
- 内存溢出:无法将所有QQ号一次性加载到内存中进行排序。
- 不适用于外部数据:标准的内存排序算法不适用于处理超出内存容量的数据集。
2.4、总结
以上这些基础的内存内去重方法,都因为“40亿QQ号”的巨大数据量和“1G内存”的严格限制而无法直接应用。我们需要寻找一种能够以更紧凑的方式表示数据存在性,或者能够分批处理数据的方法。
接下来,介绍几种针对海量数据去重的高效解决方案。
3、位图(Bitmap)解决方案
位图(Bitmap),也称为位数组(Bit Array),是一种非常节省空间的数据结构,特别适用于对大量非负整数进行“存在性”判断的场景。它的核心思想是利用一个比特位(bit)来表示一个整数的“存在”或“不存在”状态。
3.1、位图原理
假设我们要对一系列整数进行去重,并且这些整数的范围是有限的(例如,从0到N)。
可以创建一个足够大的位数组,数组的每个索引代表一个整数,而该索引位置上的比特位的值(0或1)则表示该整数是否出现过。
- 初始化:创建一个长度为
N+1的位数组,所有位都初始化为0。 - 标记存在:当遇到一个整数
X时,将位数组中索引为X的比特位设置为1。 - 判断重复:当再次遇到整数
X时,检查位数组中索引为X的比特位。如果该位已经是1,则表示X已经出现过(是重复的);如果该位是0,则表示X是第一次出现,将其设置为1。 - 提取不重复元素:遍历整个位数组,所有被设置为1的索引对应的整数就是不重复的QQ号。
3.2、内存消耗计算
假设QQ号是32位无符号整数,其范围是 0 到 2^32 - 1,即大约 4.29 * 10^9。
为了覆盖所有可能的QQ号,我们需要一个大小为 2^32 的位数组。
- 总比特数:
2^32比特 - 转换为字节:
2^32 比特 / 8 比特/字节 = 2^29 字节 - 转换为GB:
2^29 字节 / (1024 * 1024 * 1024) 字节/GB = 2^29 / 2^30 GB = 0.5 GB
所以,使用位图来存储40亿个QQ号的存在性,理论上只需要0.5GB的内存。
与1GB内存限制的比较:
0.5GB远小于1GB的内存限制。这意味着位图方案在内存上是完全可行的。
3.3、位图的优缺点
优点:
- 极度节省内存:每个数字只占用1个比特位,相比直接存储数字(4字节)或哈希表(28+字节),内存效率极高。
- 效率高:插入和查找操作的时间复杂度都是O(1),只需要进行位运算。
- 天然有序:遍历位图时,可以自然地按升序获取不重复的QQ号。
缺点:
- 数据范围限制:位图适用于数据范围相对密集且不大的场景。如果QQ号的范围非常稀疏,或者最大值非常大,位图会占用大量内存,即使实际存在的QQ号数量不多。例如,如果QQ号的最大值是
2^60,那么位图将需要2^60 / 8字节的内存,这是不可接受的。 - 无法存储额外信息:位图只能表示“存在”或“不存在”,无法存储与QQ号相关的其他信息(如QQ号出现的次数)。
- 不适用于负数或非整数:位图只能用于非负整数的去重。
3.4、总结
对于“40亿QQ号,1G内存”这个面试题,如果QQ号的范围是32位无符号整数,那么位图是一个非常理想的解决方案,因为它能够以极低的内存消耗(0.5GB)完成去重任务,并且具有O(1)的查找和插入效率。
这是在精确去重且数据范围可控情况下的首选方案。
4、布隆过滤器(Bloom Filter)解决方案
布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于判断一个元素是否可能在一个集合中。它的特点是,如果判断一个元素不在集合中,那么它一定不在;如果判断一个元素在集合中,那么它可能在(存在一定的误判率)。
4.1、布隆过滤器原理
布隆过滤器由一个很长的位数组(Bit Array)和k个哈希函数组成。
- 初始化:创建一个长度为
m的位数组,所有位都初始化为0。 - 添加元素:当要向布隆过滤器中添加一个元素
X时,将X分别通过k个独立的哈希函数计算出k个哈希值。每个哈希值对应位数组中的一个索引。然后,将这k个索引位置上的比特位都设置为1。 - 查询元素:当要查询一个元素
Y是否可能在集合中时,同样将Y通过k个哈希函数计算出k个哈希值,得到k个索引。然后,检查位数组中这k个索引位置上的比特位。- 如果所有
k个比特位都是1,则布隆过滤器判断Y可能在集合中。之所以是“可能”,是因为不同的元素经过哈希函数计算后,可能会得到相同的索引,导致“误判”(False Positive)。 - 如果其中任何一个比特位是0,则布隆过滤器判断
Y一定不在集合中。
- 如果所有
误判率(False Positive Rate):
布隆过滤器存在误判,即一个元素可能不在集合中,但被判断为在集合中。误判率与位数组的长度 m、哈希函数的个数k以及集合中元素的数量n有关。误判率p的计算公式为:
p = (1 - e^(-kn/m))^k
其中:
m是位数组的长度(比特数)。n是集合中元素的数量(预期要插入的元素数量)。k是哈希函数的个数。
为了达到最小误判率,哈希函数的最佳个数 k 可以通过以下公式计算:
k = (m/n) * ln(2)
4.2、内存消耗计算
对于“40亿QQ号,1G内存”的问题,我们假设最多有n = 40亿个不重复的QQ号。我们需要在1GB内存限制下,尽可能降低误判率。
1GB内存 = 1 * 1024 * 1024 * 1024字节 = 8 * 1024 * 1024 * 1024 比特 = 8 * 2^30 比特。
所以,位数组的长度m = 8 * 2^30比特。
我们可以根据 n 和 m 来计算在给定内存下的最小误判率 p 和最佳哈希函数个数 k。
n = 4 * 10^9m = 8 * 10^9 (近似值,方便计算)
计算最佳哈希函数个数
k:k = (m/n) * ln(2) = (8 * 10^9 / 4 * 10^9) * ln(2) = 2 * ln(2) ≈ 2 * 0.693 ≈ 1.386由于k必须是整数,我们可以选择k=1或k=2。通常选择接近计算结果的整数。 如果选择k=1,误判率会很高。 如果选择k=2,误判率会降低。 我们取k=2进行计算。计算误判率
p:p = (1 - e^(-kn/m))^kp = (1 - e^(-2 * 4*10^9 / 8*10^9))^2p = (1 - e^(-1))^2p = (1 - 0.367879)^2p = (0.632121)^2 ≈ 0.3996
这意味着,在1GB内存(80亿比特)和40亿个QQ号的条件下,如果使用2个哈希函数,误判率将高达约 40%。这个误判率对于QQ号去重来说是不可接受的,因为这意味着有40%的概率会把一个新QQ号误判为已存在的QQ号,导致去重不准确。
如果我们需要更低的误判率,例如 p = 0.01% (0.0001),需要多少内存呢?
我们可以反过来计算 m:
m = -n * ln(p) / (ln(2))^2
m = -4 * 10^9 * ln(0.0001) / (ln(2))^2ln(0.0001) ≈ -9.21ln(2) ≈ 0.693(ln(2))^2 ≈ 0.48
m ≈ -4 * 10^9 * (-9.21) / 0.48m ≈ 36.84 * 10^9 / 0.48m ≈ 76.75 * 10^9 比特
转换为GB: 76.75 * 10^9 比特 / 8 比特/字节 / (1024 * 1024 * 1024) 字节/GB ≈ 9.5 GB
这意味着,如果要求误判率达到0.01%,则需要大约 9.5 GB 的内存,这远超1GB的限制。
4.3、布隆过滤器的优缺点
优点:
- 空间效率极高:相比哈希表和位图,在允许一定误判率的情况下,布隆过滤器能以非常小的空间存储大量元素的存在信息。
- 时间效率高:插入和查询操作的时间复杂度都是O(k),其中k是哈希函数的个数,通常k很小,所以操作速度非常快。
- 保密性好:不存储元素本身,只存储元素的哈希映射,对隐私数据有一定的保护作用。
缺点:
- 存在误判率(False Positive):这是布隆过滤器最主要的缺点。它可能会把不存在的元素误判为存在。误判率随着插入元素的增多而增大,且无法避免。
- 不能删除元素:一旦一个比特位被设置为1,它可能被多个元素的哈哈希值共享。如果删除一个元素,将其对应的比特位设置为0,可能会影响到其他元素的判断,导致误判率增加或错误判断。
- 无法获取原始数据:布隆过滤器只能判断元素是否存在,无法获取元素本身。
- 对数据范围不敏感:与位图不同,布隆过滤器不需要知道元素的具体范围,只需要知道元素的数量
n。
4.4、总结
布隆过滤器在内存受限且允许一定误判率的场景下非常有用,例如缓存穿透的预防、垃圾邮件过滤等。然而,对于“40亿QQ号去重”这个场景,如果要求精确去重(即0误判率),布隆过滤器并不适用,因为它无法保证100%的准确性。如果面试官允许一定的误判率,那么布隆过滤器可以作为一种备选方案,但需要根据可接受的误判率来计算所需的内存,通常会超出1GB的限制。
因此,在严格要求精确去重且内存极度受限的情况下,布隆过滤器不是最佳选择。
5、分治(Divide and Conquer)解决方案
当数据量过大无法一次性载入内存时,分治策略是处理海量数据问题的常用方法。其核心思想是将大问题分解为若干个小问题,分别解决,最后将小问题的结果合并,从而解决原问题。对于QQ号去重问题,分治策略主要有两种实现方式:外部排序和哈希分桶。
5.1、外部排序(External Sort)
原理:
外部排序适用于数据量远超内存容量的情况。
基本思想是:
- 分块读取与内存排序:将大文件中的QQ号数据分块读取到内存中。每块数据的大小应小于可用内存。在内存中对每块数据进行排序(例如使用快速排序),并去除块内的重复项。然后将排序并去重后的数据写入临时文件。
- 多路归并:当所有数据块都处理完毕并生成了多个有序的临时文件后,使用多路归并算法将这些临时文件合并成一个大的有序文件。在归并过程中,可以很容易地识别并去除跨文件块的重复项。
详细步骤:
分块与初步去重:
- 从包含40亿QQ号的原始文件中,每次读取
M个QQ号到内存中(M的大小取决于可用内存,例如,如果1GB内存,每个QQ号4字节,可以读取1GB / 4字节 = 2.5亿个QQ号)。 - 在内存中,使用
std::unordered_set或std::sort+ 遍历的方式对这M个QQ号进行去重和排序。由于内存限制,这里只能处理一部分数据,所以std::unordered_set可能仍然会内存溢出,更稳妥的方式是直接使用std::sort进行排序,然后遍历去重。 - 将去重后的有序QQ号写入一个新的临时文件。重复此过程,直到原始文件中的所有QQ号都被处理完毕,生成多个有序的临时文件(例如
temp_0.txt,temp_1.txt, ...,temp_N.txt)。
- 从包含40亿QQ号的原始文件中,每次读取
多路归并与最终去重:
- 打开所有临时文件,并为每个文件维护一个文件指针。每次从每个文件中读取一个QQ号,组成一个“当前值”集合。
- 从“当前值”集合中找出最小的QQ号,将其写入最终结果文件。如果下一个最小的QQ号与当前写入的QQ号相同,则跳过(去重)。
- 从该最小QQ号所属的临时文件中读取下一个QQ号,更新"当前值"集合,重复上述过程,直到所有临时文件都读取完毕。
内存消耗分析:
- 分块阶段:每次只将一部分QQ号加载到内存中进行处理。内存消耗主要取决于每次加载的块大小。例如,每次加载2.5亿个QQ号,需要1GB内存。如果使用
std::sort,则需要2.5亿 * 4字节 = 1GB内存。如果使用std::unordered_set,则需要2.5亿 * (4+额外开销)字节,可能仍然会超出1GB。- 优化:在分块阶段,如果内存仍然紧张,可以不进行内存内去重,只进行排序。将排序后的块写入临时文件。去重操作可以完全放到归并阶段进行。
- 归并阶段:需要同时打开多个临时文件,并从每个文件中读取一个QQ号。内存消耗主要用于存储每个文件的当前QQ号(
N_files * 4字节)和一个小型的优先队列(用于找出最小QQ号)。这部分内存消耗非常小,远低于1GB。
优缺点:
- 优点:
- 内存效率高:可以处理任意大的数据量,只要有足够的磁盘空间。
- 精确去重:能够保证100%的去重准确性。
- 结果有序:最终输出的QQ号是排序好的。
- 缺点:
- I/O开销大:需要多次读写磁盘,包括读取原始文件、写入临时文件、读取临时文件、写入结果文件,I/O操作是主要瓶颈。
- 时间复杂度高:排序和归并过程的时间复杂度较高,通常为
O(N log N),其中N是总QQ号数量。
5.2、哈希分桶(Hash Bucketing)
原理:
哈希分桶是一种将大数据集分散到多个小数据集的方法,每个小数据集都可以独立处理。其核心思想是利用哈希函数将QQ号映射到不同的“桶”中,确保相同的QQ号一定会被映射到同一个桶。
详细步骤:
哈希分桶:
- 遍历40亿个QQ号。对于每个QQ号
Q,计算其哈希值hash(Q)。 - 根据哈希值
hash(Q)将QQ号分配到K个不同的文件中(即“桶”)。例如,可以使用hash(Q) % K来决定写入哪个文件。这里的K需要根据可用内存和每个桶的预期大小来确定。 - 关键在于,要保证
K足够大,使得每个桶文件的大小都能够被加载到内存中进行处理。
- 遍历40亿个QQ号。对于每个QQ号
桶内去重:
- 逐个读取每个桶文件。对于每个桶文件,由于其大小已控制在内存范围内,可以将其所有QQ号加载到内存中。
- 在内存中,使用
std::unordered_set或std::sort+ 遍历的方式对桶内的QQ号进行去重。 - 将去重后的QQ号写入最终结果文件。
内存消耗分析:
- 分桶阶段:内存消耗很小,每次只读取一个QQ号,计算哈希值,然后写入对应的文件。主要开销是打开和写入
K个文件。 - 桶内去重阶段:每个桶文件的大小必须小于可用内存。假设我们有1GB内存,每个QQ号4字节。那么每个桶文件最大可以存储
1GB / 4字节 = 2.5亿个QQ号。如果原始数据有40亿个QQ号,那么至少需要40亿 / 2.5亿 = 16个桶。实际中,为了留出哈希表的额外开销,桶的数量会更多,例如32个或64个桶。
优缺点:
- 优点:
- 内存效率高:可以处理任意大的数据量,只要有足够的磁盘空间。
- 精确去重:能够保证100%的去重准确性。
- 可并行化:每个桶的去重过程是独立的,可以并行处理,提高效率。
- 缺点:
- I/O开销大:需要两次遍历原始数据(一次分桶,一次桶内去重),以及大量的磁盘读写操作。
- 哈希函数选择:需要选择一个好的哈希函数,以确保QQ号均匀分布到各个桶中,避免数据倾斜。
5.3、总结
外部排序和哈希分桶都是处理海量数据去重的有效分治策略。它们都通过将大文件拆分成小文件,然后逐个处理小文件的方式来规避内存限制。
在实际应用中,哈希分桶通常更受欢迎,因为它更容易并行化,并且在某些情况下,如果哈希函数设计得当,可以避免全局排序的开销。
对于“40亿QQ号,1G内存”的问题,这两种方法都能提供精确的解决方案,但都需要权衡磁盘I/O和处理时间。
6、各种方案的优缺点总结与最终结论
经过对哈希表、红黑树、位图、布隆过滤器、外部排序和哈希分桶等多种去重方案的分析,我们可以对它们在“40亿QQ号,1G内存”这个特定场景下的适用性进行总结和比较。
6.1、方案对比总结
| 方案名称 | 原理概述 | 内存消耗(40亿QQ号) | 准确性 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 哈希表 | 存储所有不重复QQ号,O(1)查找 | ~32 GB | 精确 | 查找/插入效率高(平均O(1)) | 内存消耗巨大,远超1GB限制 |
| 红黑树 | 存储所有不重复QQ号,O(logN)查找 | ~112 GB | 精确 | 查找/插入效率高(O(logN)),数据有序 | 内存消耗巨大,远超1GB限制 |
| 位图(Bitmap) | 每个QQ号对应1比特位,标记存在性 | 0.5 GB | 精确 | 内存效率极高,查找/插入O(1),结果有序 | 适用于数据范围密集且不大,不适用于稀疏数据 |
| 布隆过滤器 | 多个哈希函数映射到位数组,概率性判断 | 1 GB (误判率~40%) | 概率性 | 空间效率高,查找/插入O(k) | 存在误判率,不能删除,无法获取原始数据 |
| 外部排序 | 分块内存排序,多路归并去重 | 1 GB (分块内存) | 精确 | 可处理任意大数据量,精确去重,结果有序 | I/O开销大,时间复杂度高(O(N log N)) |
| 哈希分桶 | 哈希到多个小文件,桶内内存去重 | 1 GB (桶内内存) | 精确 | 可处理任意大数据量,精确去重,可并行化 | I/O开销大,需良好哈希函数,可能数据倾斜 |
6.2、最终结论与最佳实践
针对“40亿QQ号,1G内存,怎么去重?”这个面试题,我们需要在内存限制和去重准确性之间找到平衡。
如果QQ号是32位无符号整数,且要求100%精确去重:
- 位图(Bitmap)是最佳方案。仅需0.5GB内存即可覆盖所有可能的32位QQ号,完美满足1GB内存限制,且去重结果精确,效率极高。这是最符合题目要求且性能最优的方案。
如果QQ号的范围非常大(例如64位整数),或者数据非常稀疏,导致位图内存超限,且要求100%精确去重:
- 哈希分桶(Hash Bucketing)是最佳方案。这种方法通过将40亿个QQ号分散到多个小文件中,每个小文件的大小控制在1GB内存以内,然后对每个小文件进行内存去重。虽然会产生大量的磁盘I/O,但它能够保证去重的精确性,并且可以处理任意大的数据量。具体实现时,需要选择一个好的哈希函数,将QQ号均匀地分配到大约16-32个文件中(例如,
hash(QQ号) % N_files),确保每个文件的大小在1GB以内,然后对每个文件单独使用std::unordered_set进行去重。 - 外部排序也是一个可行方案,但通常在实际应用中,哈希分桶因其更好的并行化潜力而更受欢迎。
- 哈希分桶(Hash Bucketing)是最佳方案。这种方法通过将40亿个QQ号分散到多个小文件中,每个小文件的大小控制在1GB内存以内,然后对每个小文件进行内存去重。虽然会产生大量的磁盘I/O,但它能够保证去重的精确性,并且可以处理任意大的数据量。具体实现时,需要选择一个好的哈希函数,将QQ号均匀地分配到大约16-32个文件中(例如,
如果允许一定的误判率(例如,可以接受千分之一或万分之一的误判),且追求极致的内存效率:
- 布隆过滤器可以考虑。但如前所述,在1GB内存下处理40亿个元素,即使是布隆过滤器,要达到较低的误判率(如0.01%),也需要远超1GB的内存(约9.5GB)。如果题目对误判率没有明确要求,或者允许较高的误判率(例如,40%),那么布隆过滤器在1GB内存下可以工作,但其结果可能不符合“去重”的实际需求。
总结
面试官提出这个问题,通常是希望考察你对位图、布隆过滤器、哈希分桶等大数据处理技术的理解和应用。
最直接且高效的答案是位图,因为它在32位QQ号的假设下,完美契合了内存和精确去重的要求。
如果面试官追问QQ号范围更大或更稀疏的情况,则应转向哈希分桶或外部排序。
在回答时,清晰地阐述每种方案的原理、内存计算、优缺点,并根据题目给出的约束条件(特别是QQ号的范围和内存限制)进行选择和解释,将能充分展现你的技术深度和解决问题的能力。
