大数据题目解题技巧:
1)哈希函数可以把数据按照种类均匀分流
2)布隆过滤器用于集合的建立与查询,并可以节省大量空间
3)一致性哈希解决数据服务器的负载管理问题
4)利用并查集结构做岛问题的并行计算
5)位图解决某一范围上数字的出现情况,并可以节省大量空间 6)利用分段统计思想、并进一步节省大量空间 7)利用堆、外排序来做多个处理单元的结果合并
这类问题面试官希望我们可以提问题,把模糊的问题抽象化
一、题目一
32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文 件,所以在整个范围中必然存在没出现过的数。可以使用最多1GB的内存,怎么找到所有 未出现过的数?
解法一:比特数组,范围是:[0 到 pow(2,32)-1]
。如果准备一个 bit 数组,数组需要:pow(2, 32) / 8 = 500M
左右,则可以把所有的整数存储在这个数组中,直接遍历即可
进阶:内存限制为 3KB,但是只用找到一个没出现过的数即可
解法二:利用词频统计不够,划分范围来解决
- 比如给内存 3KB,首先要确认数组长度,找到所给内存能申请数组的长度(离2的N次方最近的)(
3*1024 / 4
最接近 512)(4 是无符号整数) - 把所给的整数按范围分成 512 份。范围一共是
pow(2, 32)
个数字。 一份是pow(2,32) / 512 = pow(2, 23) = 8388608
,比如第一份表示范围0 - 8388608
。第二份表示8388609 - 8388608+8388608
。依次类推 - 然后遍历这 40 亿个无符号整数,这些整数处于那个范围,对应数组的那个元素,就给数组的那个元素自增。因为40 亿个无符号肯定有那个范围的数字没有,导致数组的那个元素的值不为 8388608。找到这个整数范围。在将这个整数范围划分成 512 份,遍历所有整数。依次类推,总能找到没有出现的数字
高级:假设只能申请有限几个变量,找到一个没出现过的数
二分的思路,将这个范围先进行二分,定义两个变量 A 和 B,分别对应两个范围 [0 - pow(2, 16)-1] 和 [pow(2, 16), pow(2, 32)]
。 遍历所有整数,落在 [0 - pow(2, 16)-1]
区间的让 A 变量自增,同理,落在 [pow(2, 16), pow(2, 32)]
区间的让 B 变量自增。肯定有个区间,有个变量的不够 pow(2, 16)
这么大,因为只有 40 亿个数字,那就继续在这个区间查找,继续二分。最多 32 次二分,也就是最多遍历 32 次所有整数。即可找到没有出现过的数
二、题目二
有一个包含100亿个URL的大文件,假设每个URL占用64B,请找出其中所有重复的URL
- 哈希分流,分流到多个小文件,然后在小文件中进行寻找重复的 URL,然后合并。
- 布隆过滤器,但是会有一定的失误率。边添加这个集合的时候边查询,每次添加之前先查询看能否找到这个 URL,如果可以找到就把它记录在最终的文件中
【补充】某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门Top100 词汇的可行办法
- 哈希分流,将大文件通过哈希函数将 URL 分流到多个小文件。然后分别统计出每个小文件中的词频个数
- 每个小文件中的词频通过一个大根堆进行排名,得到一个小文件的词频排名
- 建立一个总的大根堆,用于汇总所有的小文件的大根堆。如何汇总。先取所有小根堆上的堆顶元素,放到总的大根堆。然后总的大根堆依次弹出,弹出那个小文件的大根堆的元素,就从那个小文件的大根堆去补元素。即可求出 Top 任意
三、题目三
32位无符号整数的范围是0~4294967295,现在有40亿个无符号整数,可以使用最多1GB的 内存,找出所有出现了两次的数。
哈希函数分流分成小文件,然后对于每个小文件进行内存操作,最后汇总即可
也可以使用两位(两个bit)来存储每个整数的出现频率。比如:00 表示 0 次,01 表示 1 次,10 表示 2 次,11 表示大于 2 次。那么总共需要 pow(2, 32) * 2 / 8 = pow(2, 30)
字节,差不多刚好 1G 的空间。
【补充】 可以使用最多10MB的内存,怎么找到这40亿个整数的中位数?
划分范围的思想
- 有 10M 内存,首先确认数组的长度,找到所给内存能申请数组的最大长度(离 2 的 N 次方最近的)(
10M / 4B = 2621440
)(4 是无符号整数),假设数组长度为 X 。 - 把所给的整数按范围划分成 X 份,整数范围一共是
pow(2, 32)
个数字。划分成 X 份。假设每份会有 Y 个整数。比如第一份是[0, Y-1]
、[Y, 2*Y-1]
依次类推。 - 然后遍历这 40 亿个整数,这些整数处于那个范围,对应到数组上的那个元素,就让数组的那个元素自增。
- 然后再遍历数组,因为要找中位数,用一个变量,依次增加数组元素,增加到大于等于 20 亿停止,就确认了中位数存在这个范围。然后再这个范围划分成 X 份,依次进行上述过程。即可最终确定中位数
四、题目四
假设有一个 10G 的文件,文件中存储着无序的有符号整数,只给 5G / 5M / 5K 的内存。将这些整数排序,然后输出到一个文件
解法一:范围内统计(小根堆)
- 先划分范围,拿到所给定的内存,建立小根堆,堆中元素为
[整数、词频]
,注意小根堆中是按照整数的大小进行排序的。 - 比如,整数占 4 个字节,词频占 4 个字节。堆中一个元素就是 8 个字节。那 5M 的内存可以容纳:
5 * pow(2,20) / 8 = pow(2, 19)
,记做 X 。 - 然后把有符号整数这个范围
[-pow(2, 31), pow(2, 31)-1]
进行分割,每份 X 个元素,分成 N 个。变成[0, X-1]
、[X, 2*X-1]
等等。这些区间之间就是递增的 - 然后遍历这个 10G 的文件,先去填充第一个区间,也就是填充小根堆中整数和词频。第一个区间也就是整个文件最小的数字集合。通过小根堆也就是排好序了,然后输入到结果文件。依次利用小根堆拍第二个区间、第 N 个区间。
解法二:多次遍历记录当前极大值
- 拿到给定内存,建立大根堆,堆中元素为
[整数、词频]
,注意大根堆中是按照整数的大小进行排序的。 - 比如,整数占 4 个字节,词频占 4 个字节。堆中一个元素就是 8 个字节。那 5M 的内存可以容纳:
5 * pow(2,20) / 8 = pow(2, 19)
,记做 X 。也就是说 这个堆中可以容纳 X 个元素 - 然后遍历这个 10G的文件,依次输入大根堆整数,如果相等,就增加词频,如果大于堆顶元素,就跳过,如果小于堆顶元素,就加入到堆中(堆满的时候要弹出最大元素)。这样遍历完之后,我们排序好了堆中的元素。也就是10G 文件中最小的一批元素,我们同时使用一个变量 Y 记住当前的堆顶元素的整数值。然后将这个堆中元素输出到文件中。
- 再次遍历这个 10G 的文件,因为在上一次遍历中已经记住了上一批元素的最大整数 Y。这次遍历文件遇到小于等于整数 Y 的直接跳过。遍历完更新 Y 的值,然后输出堆中元素到文件中
- 依次遍历,总有一次 Y 的值等于有符号整数范围最大值,或者某次堆中元素不满的情况,就说明总体排序完成。
五、题目五
位运算的题目
给定两个有符号32位整数a和b,返回a和b中较大的。要求:不用做任何比较判断。
1 | class BitOperation { |
六、题目六
判断一个32位正数是不是2的幂、4的幂
2 的幂含义:一个数的二进制所有位上只有一个位为 1
x & (x-1) == 0
可以判断。或者把那个位上的 1 取出来,和原数(2^n
)比较即可。
4 的幂含义:首先这个数的二进制所有位上只有一个位上为 1,并且这个位在 0、2、4、6...
位置
- 首先判断是不是只有一个 1,也就是判断是 2 的幂
- 然后
x & 010101...01
判断是否为 0,为 0 则不是,不为 0 则是
1 | bool is2Power(int n) { |
七、题目七
给定两个有符号32位整数a和b,不能使用算术运算符,分别实现a和b的加、减、乘、除运 算
【要求】如果给定a、b执行加减乘除的运算结果就会导致数据的溢出,那么你实现的函数不必对此 负责,除此之外请保证计算过程不发生溢出
两数相加:
- 两个二进制数异或不会产生进位,也称为无进位相加
- 两个二进制数相与,然后向左移一位,结果就是两数相加之后的进位信息
因此,两数相加等于 (这两个数异或的值 + 这两个数相与然后左移一位的值),那么括号里的相加又等于 (两个数异或的值 + 两个数相与然后左移一位的值)。依次类推,直到进位为0,也就是两个数相与为 0 时。两数异或的结果就是最终值
1 | // a+b 不能溢出 |
两数相乘
1 | int multi(int a, int b) { |
两数相除
1 | bool isNeg(int n) { |