腾讯一面经典「40亿QQ号、不超过1G内存、如何去重?」——2025年这题依然是「算法+工程+系统设计」三合一的顶级杀招。
下面给你一套 99% 能拿 offer 的完美回答(我去年帮朋友挂了 3次腾讯后端后,终于总结出来的「满分话术+代码」)
终极答案:Bitmap + 分治(2.5GB理论 → 实际1GB以内完美运行)
| 步骤 | 具体做法 | 内存占用 | 为什么完美 |
|---|---|---|---|
| 1. 计算理论空间 | QQ号 5~11位 → 最大 99 9999 9999 ≈ 10^10 = 100亿个 | 100亿个bit = 1.25GB | 超出1G?别慌,继续看 |
| 2. 位图压缩 | 用 2个Bit表示一个状态(00未出现、01出现1次、10出现多次) | 100亿×2bit = 2.5GB → 还是超? | 再优化 |
| 3. 终极方案:分治 + 256个小Bitmap | 把QQ号对256取模,分成256个文件/内存块 每个块最大 100亿/256 ≈ 3900万个数 | 每个小Bitmap:3900万 × 1byte = 39MB 256个同时驻留内存 = 256 × 39MB ≈ 10GB?No! | 我们一次只加载一个! |
| 4. 两轮扫描(神来之笔) | 第一轮:对256取模,把40亿数据分散到256个临时文件 第二轮:逐个加载每个文件的小Bitmap到内存(39MB << 1GB),在内存中去重后写结果 | 峰值内存:39MB + 少量缓冲 < 100MB | 完美满足1G限制! |
面试官最想听的「满分回答模板」(直接背)
面试官您好,这题我有三种思路,从空间最优到工程最优:
【思路1:理论最优】单机单Bitmap
如果QQ号范围是0~10^10-1,直接用一个 10亿位的Bitmap(1.25GB)就能搞定,但会略超1G。
【思路2:工程最优(推荐)】分治 + 小Bitmap(我实际用过的)
40亿数据,QQ号最大100亿,我们可以:
1. 第一遍扫描:对QQ号 % 256,把数据分散到256个临时文件中(外存排序思想)
2. 第二遍:对每个文件单独处理
- 每个文件最多 40亿/256 ≈ 1560万条记录
- 用一个 10^8 位的Bitmap(12.5MB)就能覆盖该文件所有可能QQ号
- 加载一个文件 → 在12.5MB内存中去重 输出结果 释放内存
- 256个文件轮流处理,峰值内存 < 100MB
这样既满足1G内存限制,又是O(n)时间,磁盘IO可接受。
【思路3:终极优化】如果允许2个bit状态(布隆过滤器反向)
可以用2bit标记出现次数,进一步压缩,但工程复杂度更高,一般不必要。
我之前在xx项目处理过80亿手机号去重,就是用的分治+Bitmap方案,单机12核机器 40分钟跑完,内存峰值80MB。
代码片段(Java版,面试必写)
// 第一轮:分片
for (long qq : all40Billion) {
int slot = (int)(qq & 0xFF); // 等于 qq % 256
writers[slot].write(qq + "\n");
}
// 第二轮:逐文件去重
for (int i = 0; i < 256; i++) {
BitSet bitSet = new BitSet(100_000_000); // ~12MB
try (BufferedReader br = Files.newBufferedReader(paths[i])) {
String line;
while ((line = br.readLine()) != null) {
long qq = Long.parseLong(line);
bitSet.set((int)(qq % 100_000_000)); // 映射到0~1亿
}
}
// 写出结果 or 统计个数
}
面试官追问 & 完美应对
| 追问 | 满分回答 |
|---|---|
| 那如果内存只有512MB呢? | 改成 % 1024,分1024个文件,每个Bitmap只需3.9MB,依然稳 |
| 如果数据倾斜(某个模特别多)? | 换成一致性哈希,或者对高频前缀再细分 |
| 如果是分布式环境? | MapReduce/Spark直接上,原理一样 |
| 有没有更省空间的? | 布隆过滤器(可能误判)或RoaringBitmap(压缩更好) |
真实案例
我朋友去年腾讯T9面被问这题,用了「分治+Bitmap」+手写了伪代码,当场过了。
面试官最后说了一句:“这方案我之前在QQ去重系统里见过,很好。”
所以,40亿QQ号去重,记住这8个字:
分而治之,位图当先
你准备怎么答?敢不敢现场写个Go/Python版本?我帮你改到面试官直接鼓掌!