会慢慢补充遇到过的大数据量题目。
前置知识
可以阅读这些文章快速了解:
解题技巧
很重要!没有思路的时候一条一条想!
1、统计 40 亿个整数中出现次数最多的数,只有 1 G 内存,磁盘无限制
分析
正常来说,我们一般会想到用哈希表来统计词频,也就是 HashMap<Integer, Integer>,其中,key 表示数字,value 表示这个数字的出现次数。但在 Java 中 Integer 类型的占用空间是 4 Byte,我们暂时不考虑其他的占用空间,这样一个数的 key + value 的占用空间就是 8 Byte。假设最差情况,40 亿个数字,出现最多次数的数只出现了两次,也就是说我们需要保存 40 亿 - 1 个数,40 亿大约是 232。所以我们可以计算一下大概要花费的空间:232 * 8 Byte = 235 Byte = 32 GB。所以说这种方式肯定不符合题目的条件的。
思路
我们可以使用哈希函数对 40 亿个数字进行分类这种方式。具体步骤如下:
(1)在磁盘上申请 100 个文件(具体申请多少个文件视情况而定)
(2)对 40 亿个数依次进行遍历,对遍历到的数字用哈希函数计算出一个哈希值,然后在模除文件的个数,也就是 % 100 ,这样就能均匀的分配到 100 个文件中(相等的数他们的哈希值肯定是一样的,所以一样的数肯定能保存到一个文件中)
(3)依次对 100 个文件进行统计,统计每个文件中出现最多的数字,如何统计呢?我们可以使用哈希表进行统计,key 还是数字,value 还是出现次数。正常情况下,文件中的数大概是 40 亿 / 100 个,也就是只占用 32 GB / 100 = 0.32 GB,内存足够。
(4)再用一个哈希表保存每个文件出现最多的数字和出现次数(也就是只保存 100 个数)。然后再依次遍历哈希表,找出现次数最大值即可。
注意:假设出现最差情况,一个数出现了 40 亿 - 1 次,这意味着一个文件中保持了将近 40 亿个数,也就是 32 GB 左右,但是也没事。我们统计的时候是分批读入数字,并且哈希表肯定不会溢出,因为你保存的本来就是 数字 + 出现次数,假设 2 出现了 40 亿次,我们在哈希表中就记录 2 : 40 亿 不就行了吗,也就是只记录一个数就行了。
2、假设有 40 亿个整数,统计 0 ~ 42 亿范围中没出现过的数
分析
正常来说,我们想到的是范围多大就开多大的数组,只要出现过就在数组对应的下标标记,最后我们遍历数组,没有标记过的位置就是范围内没出现过的数。一个 int 类型在 Java 中是 4 个字节,计算空间占用:42 亿 * 4 Byte,大概为 16 GB,不符合题意。
思路
我们可以使用位图的方式来做,也就是 bitMap。具体步骤如下:
(1)我们用 1 bit 来表示数字是否出现过,也就是说我们可以开 int[ 42 亿 / 8] 这么大的数组(一个 Byte = 8 bit),这里的 42 亿是范围,其实意思就是说我们的思路没有变化,只是换了一种数据的存储方式。空间占用:42 亿 Byte / 8,大概是 500 多 MB,满足条件。
(2)然后遍历这 40 亿个数,依次在对应的 bit 位上标为 1。
(3)最后统计我们开的数组,bit 位上为 0 的就是没出现过的。
进阶
我们可以使用范围划分的方式。具体步骤如下:
(1)10 MB 大概可以申请 int[2621440] 这么多数组空间,计算方式:10 MB / 4Byte = 2621440
(2)划分份数:42 亿 / 2621440 大概等于 1638,假设这个数组名为 arr,arr[0] 就代表 0 ~ 1637 这个范围出现的数字的个数,同理 arr[1] 就代表 1638~ (1638+ 1638- 1) 这个范围出现的数字的个数。也就是类似于词频统计。
(3)依次遍历这 40 亿个数,arr[每个数 / 1638]++,也就是说 0 就是 arr[0]++,1638就是 arr[1]++,其实思路就是把范围划分成 1638份,每份统计对应的数字个数,如果对应的数字个数小于 1638,说明在这个范围内肯定有数字没出现过
(4)重复此过程,将范围逐渐缩小即可求出答案。
注意:这里是因为能使用的空间比较大,所以只需一次划分即可。
再进阶
只能使用有限几个变量,如何做?
二分法,一直二分下去。
3、有一个包含 100 亿个 URL 的大文件,找出所有重复的 URL
思路
第一种办法:使用哈希函数进行分类。
(1)申请 N 个磁盘文件
(2)遍历所有的 URL,为每个 URL 计算哈希值,然后再模除 N,就能均匀地分类到对应的文件中,重复的 URL 一定会在同一个文件中。
(3)依次统计每一个文件中重复的 URL,然后再汇总各个文件的信息即可。
第二种办法:使用布隆过滤器。会有失误率,不过概率很低。
(1)首先申请一个足够大的 bitMap,便建立边检查是否重复
(2)遍历所有的 URL,遍历到一个 URL,就通过某种哈希函数计算它的哈希值,然后再模除 bitMap 的位数。
(3)如果发现对应的槽位已经为 1 (初始都是为 0,一个 bit 位就两种状态,0 和 1),就说明这个 URL 是重复的。
(4)如果发现对应的槽位为 0,说明不重复,将槽位置为 1。
可以发现,如果重复的 URL 一定能找出来,但是可能不重复的 URL 也被找出来了(这也就是失误率,因为哈希冲突),如果可以允许有一定的失误率就可以使用这种办法。
4、百亿数据量中找到热门 Top 100
思路
使用分类 + 大根堆方式。
(1)通过哈希函数将海量数据分类到一个个小文件中
(2)依次对每个小文件建立大根堆,规则就是 URL 出现次数最多的就在大根堆的顶部
(3)取每个小文件出现次数最多的 URL,也就是刚刚建立的大根堆顶部元素,再建立一个大根堆
(4)这样建立出来的大根堆就是 Top 100 了
5、40 亿个数中只出现了两次的数
思路
第一种方式:哈希函数。
(1)通过哈希函数对 40 亿个数进行分流,分到一个个小文件中。
(2)依次用哈希表统计每个小文件中只出现了两次的数。
(3)然后再汇总各个文件中只出现了两次的数即可。
第二种方式:bitMap。
(1)正常来说我们都是用 1 bit 来表示对应的数字是否出现过,对应这个题目,我们可以变化一下。
(2)用 2 bit 来表示对应的数字,00 代表这个数没有出现过,01 代表这个数出现过一次,10 代表这个数出现过两次,11 代表这个数出现了两次以上。
(3)容量大概使用:40 亿大概就是 232,所以就是 232 * 2 bit / 8 ,大概就是 1 GB 左右。
进阶
范围统计思想。
www.bilibili.com/video/BV13g…,视频 21:51
6、10 G 的文件,文件中每个数都是无符号整数,是无序的,只给 5 G 内存,输出一个新文件,使其变成有序的
思路
小根堆方式。