大家好,我是怒码少年小码。
最近超级忙,很多实验报告,已经四五天没搞了,但是我还是回来了!
海量数据场景下的热门算法题
本篇的题目不要求写代码,面试的时候能很清楚的说出思路就可以了。
1. 从40个亿中产生一个不存在的整数
题目要求:给定一个输入文件,包含40亿个非负整数,设计一个算法,产生一个不存在该文件的整数,假设你有1GB的内存来完成这项任务
进阶:如果只有10MB的内存可用,该怎么办
位图存储大数据的原理
假设用哈希表来保存出现过的数,如果40亿个数都不同,则哈希表的记录数为40亿条,存一个32位整数需要4B,所以最差情况需要40亿 * 4B = 160亿字节,大约需要16GB的空间,不符合要求
- 40亿 * 4B = 160 亿字节,大约需要16GB
- 40亿 / 8字节 = 5亿字节,大约0.5GB的数组就可以存下40亿个
如果数据量很大,采用位方式(俗称位图)存储数据是常用的思路。
我们使用bit map(比特数组)的方式来表示数出现的情况。即申请一个长度为4294967 295的bit类型的数组bitArr(就是boolean类型),bitArr上的每个位置只可以表示0 或 1状态。8个bit 为1B,所以长度为4294967 295的bit 类型的数组占用5000MB空间,满足题目给定的要求
使用bitArr数组的方法:遍历40亿个无符号数,遇到所有的数时,就把bitArr相应位置的值设置为1。例如,遇到1000,就把bitArr[1000]设置为1
遍历完成后,再依次遍历bitArr数组,看看哪个位置上的值没被设置为1,这个数就不在40亿个数中。例如,发现bitArr[8001] == 0,那么8001就是没出现过的数,遍历完bitArr之后,所有没出现的数就都没出来了。
位存储的核心是:存储的并不是这40亿个数据本身,而是其对应的位置。
进阶:使用10MB来存储
如果只有10MB内存,位图也不能完成。使用分块思想,时间换空间,通过两次遍历完成
40亿个数采用位图至少需要0.5GB空间,如果只有10MB空间,也至少需要50个块
一般来说,划分都是使用2的整数倍,因此划分成64个块比较合理
一共只有40亿个数,所以,如果统计落在每一个区间上的数有多少,肯定有至少一个区间上的计数少于67 108 864(2^32 / 64)。利用这个可以找出其中一个没出现的数。
-
如果当前数是 3422552090, 3422552090 / 67 108 864 = 51,所以第51区间上的计数增加countArr[51]++。
-
遍历完40亿个数之后,遍历countArr,必然会有某一个位置上的值(countArr[i])小于67 108 864,表示第 i 区间上至少有一个数没出现过。此时使用的内存就是countArr的大小(64 * 4B),占用的空间非常小
-
假设找到第37区间上的计数小于67 108 864(知道哪些区间计数小于67 108 864计数),然后对40亿个数据进行第二次遍历
- 申请长度为67 108 864的bit map(比特数组),占用大约8MB空间,记为bitArr[0…67 108 864]
- 遍历这40亿个数,此时的遍历只关注落在第37区间上的数,记为num (num满足num / 67 108 864 == 37),其他区间的数全部忽略
- 如果步骤2的num在第37区间上,将bit[num - 67 108 864 * 37]的值设置为1,也就是只做第37区间上的数bitArr映射(通过相减可以得到在bit数组的位置i)
- 遍历完40亿个数之后,在bitArr上必然存在没被设置成1的位置,假设第i个位置上的值没设置成1,那么67 108 864* 37 + i这个数就是一个没出现过的数
总结:
- 根据10MB的内存限制,确定统计区间的大小,就是第二次遍历时的bitArr大小
- 利用区间计数的方式(用countArr[i]++),找到那个计数不足的区间,这个区间上肯定有没出现的数
- 对这个区间上的数做bit map映射,再遍历bit map(bit数组),找到一个没出现的数即可
如何确定分块的区间
针对上面例子中分块大小为64块的问题,为什么不是128块、32块、16块或其他类型
主要保证第二次遍历时每个块都能放进这10MB的空间。223<10MB<224,而2^23占用大约为8MB空间,也就是说一次的分块大小只能为8MB左右。上面的第二次遍历时如果分为64块,则刚好满足要求。
所以这里最少要分成64块,如果分成128块、256块也可以。
2. 用2GB内存在20亿个整数中找到出现次数最多的数
题目要求:有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数(内存限制为2GB)
思路分析:
通常在很多整数中找到出现次数最多的数,常见是使用哈希表对出现的每一个数做词频统计,哈希表的key是某一个整数,value是这个数出现的次数。
但是就这题来说,一共有20亿个数,哈希表一条记录(key,value)需要8B,当哈希表记录数为2亿个时,需要至少1.6GB内存,当出现极端情况,20亿个数都不同,内存会不够用
解决方法:
把包含20亿个数的大文件用哈希函数分成16个小文件,根据哈希函数的性质,同一种数不可能被散列到不同的小文件上,同时每个小文件中不同的数一定不会大于2亿种。假设哈希函数足够优秀,然后对每一个小文件用哈希表来统计每种数出现的次数,这样就得到了16个小文件中各自出现次数最多的数和各自次数统计,接下来再进行比较即可。
总结:
把一个大的集合通过哈希函数分配到多台机器中,或者分配到多个文件里
,这种技巧是在处理大数据面试题最常用技巧之一。
但是到底分配到多少台机器、分配到多少个文件,在解题时一定要确定下来。可能在与面试官沟通过程中由面试官指定,也可能是根据具体的限制来确定,比如本题确定分成16个文件,就是根据内存限制2GB的条件来确定。
3. 从100亿个URL中查找的问题
题目要求:有一个包含100亿个URL的大文件,假设每个URL占用64B,请找出其中所有重复的URL
补充问题:某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门 Top 100词汇的可行方法)
解答:
原问题的解法使用解决大数据问题的一种常规方法:把大文件通过哈希函数分配到机器,或者通过哈希函数把大文件拆成小文件,一直进行这种划分,直到划分的结果满足资源限制的要求。
- 首先要向面试官询问在资源上的限制有哪些,包括内存、计算时间等要求
- 明确限制要求之后,可以将每条URL通过哈希函数分配到若干台机器或者拆分成若干个小文件(这里的"若干"由具体的资源限制来计算出精确的数量)
补充问题:
最开始还是用哈希分流的思路来处理,把包含百亿数据量的词汇文件分流到不同的机器上,具体多少台机器由面试官规定或者由更多的限制来决定。
- 对每一台机器来说,如果分到的数据量依然很大,比如,内存不够或存在其他问题,可以再用哈希函数把每台机器的分流文件拆成更小的文件处理。
- 处理每一个小文件的时候,通过哈希表统计每种词及其词频,哈希表记录建立完成后,再遍历哈希表
- 遍历哈希表的过程中使用大小为100的小根堆来选出每一个小文件的Top 100(整体未排序的Top 100)。
- 每一个小文件都有自己词频的小根堆(整体未排序),将小根堆里的词按照词频排序,就得到了每个小文件的排序后Top 100。
- 然后把各个小文件排序后的Top 100进行外排序或者继续利用小根堆,就可以选出每台机器上的Top 100。同理,不同机器之间的Top 100再进行外排序或者继续利用小根堆,最终求出整个百亿数据量中的Top 100。
对于Top K的问题,除用哈希函数分流和用哈希表做词频统计之外,还经常用堆结构和外排序的手段进行处理。
例如:
- 将100亿字节的大文件通过哈希函数分配到100台机器上,然后每一台机器分别统计分给自己的URL中是否有重复的URL,同时哈希函数的性质决定了同一条URL不可能分给不同的机器;
- 或者在单机上将大文件通过哈希函数拆成1000个小文件,对每一个小文件再利用哈希表遍历,找出重复的URL;
- 还可以在分给机器或拆完文件之后进行排序,排序过后再看是否有重复的URL出现。
总结:
很多大数据问题都离不开分流
- 用哈希函数把大文件的内容分配给不同的机器
- 用哈希函数把大文件拆成小文件
- 用哈希函数把大文件拆成小文件,然后处理每一个小数量的集合
4. 40亿个非负整数中找到出现两次的数
题目要求:32位无符号整数的范围是0 ~ 4294967295,现在有40亿个无符号整数,可以使用最多1GB的内存,找出所有出现了两次的数
(这题可以看作第一题的进阶问题,将出现次数限制在了两次)
解题思路:
-
可以用bit map的方式表示数出现的情况。具体就是申请一个长度为 4294967295 * 2的bit类型的数组bitArr,用两个位置表示一个数出现的词频,1B占用8个bit,所以长度为4294967295 * 2的bit类型数组占用1GB空间
-
使用方法:遍历这40亿个无符号数
- 如果初次遇到num,就把bitArr[num * 2 + 1]和bitArr[num * 2]设置为01
- 如果第二次遇到num,就把bitArr[num * 2 + 1]和bitArr[num * 2]设置为10
- 如果第三次遇到num,就把bitArr[num * 2 + 1]和bitArr[num * 2]设置为11
- 以后再遇到num,发现此时bitArr[num * 2 + 1]和bitArr[num * 2]已经被设置为11,就不再做任何设置。
- 遍历完成后,再依次遍历bitArr,如果发生bitArr[num * 2 + 1]和bitArr[num * 2]设置为10,那么i就是出现了两次的数
END
看得很枯燥无味~但是!加油!!
参考文章1:https://juejin.cn/post/7288229413034999866
转载文章1:https://cv3c20a1rv4.feishu.cn/wiki/DViewhOX9iSxCAkmvHGcxxMsnRd