文章目录
- 题目
- 要求
- 1)内存 1G
- 2)内存 3 KB
- 3)内存 有限变量
- 举例
题目
现在有 40 亿个无符号整数
,无符号整数的范围是 0 ~ 232-1(42亿+),哪怕 40 亿个数完全不同,在该范围中也总有没有出现过的数,试问如何找到所有该范围中没出现过的数?
要求
1)内存 1G
直接使用位图
常见的存储数据单位有 int,long等,而位图则是采用位
作为单位来存储数据,使用一个或者多个位来标记某个元素对应的值
位图是通过位数组
来表示对应的元素是否存在的。像在本题中无符号整数一共有 232 个,如果使用位数组来表示每个数有没有出现过(0 表示没出现过,1 表示出现过),那么差不多只要花费 500 MB 的空间就可以表示这 232 个数
那么之后我们只需要遍历这 40 亿个数,如果出现过就将位数组中对应的位置标记为 1,如果从未出现过,那么对应的位一定还是 0
最后位数组中还是 0 的位对应的数就是我们要找的从未出现过的数们
2)内存 3 KB
内存只有 3 KB,但是只需要找到一个
没有出现过的数即可
将这 3 KB的内存都拿来申请 int 类型的数组,该数组中元素的个数为 2 的某次方,那么 3 KB最多可以申请到 2 的 9 次方大小(512)的 int 类型的数组
既然为 2 的某次方,就意味着 512 一定可以被 2 的32 次方整除,那么我们就把 2 的32 次方平均分成 512 份,每一份应该有 2 的 23 次个数
接下来,我们就可以遍历这 40 亿个数,遍历到数字 X,我们就把该数除以 2 的 23 次方,来看看,这个数属于哪个范围,将数组对应的位置的值加一
如果说一共有 2 的 32 次方个数(每个数都是不同的),那么将上面的数进行以上的操作,最后 512 个 int 的值都会是 2 的 23 次方
由于现在只有 40 亿个数,并且只要求找到一个没有出现过的数即可,那就意味着,数组中一定会有一个 int 的值不足 2 的 23 次方
既然如此,就可以将缺少的数定位到该区间内,再将该区间内的值分成 512 份,进行之前类似的操作,每一份就是 2 的 14 次方个数,周而复始,一定能够定位到没有出现过的数
3)内存 有限变量
只提供有限几个变量,如何找到一个没有出现过的数
原理和上面的方法差不多,只不过没有办法申请更多元素的 int 数组罢了,没有办法将 2 的32 次方的数分成更多份,那我们可以进行二分
,就有两个区间 [0,231-1] 和 [231,232-1]
如果只有 40 亿个数,将其遍历,找两个变量分别统计两个区间出现的数的个数,最后找到一个没有满 2 的 31 次方的区间,继续二分,那么最多分 32 次,一定能够找到一个没出现过的数
举例
如果理解有困难,我们可以将数量变小,进行举例
比如一共有 9 个数(5,8,2,0,3,2,9,4,3),这 9 个数都在 [0,9] 这个范围内,那么至少有一个数在范围内但是不在提供的 9 个数中,请找出没有出现过的数
按照位图的解法,就相当于内存足够让我们申请一个有 10 位的数组
依次遍历这 9 个数,如果出现就将对应的位的位置标记成 1,就像下面这样
遍历完成后,就会发现没出现过的数是 1、6、7
再举另外一个例子
比如一共有 9 个数(5,8,14,0,10,2,9,4,3),这 9 个数都在 [0,15] 这个范围内,那么至少有一个数在范围内但是不在提供的 9 个数中,只提供 20 个字节的内存,请找出一个没有出现过的数
按照申请数组的方法就应该是这样的
一共只有 20 个字节,那么申请的 int 类型的数组最多有 4 个元素(必须是 2 的某次方),如果将提供的 9 个数分别除以 4 来确定每个数应该存在哪个范围,从而数组对应的位置值加一,比如 9,除以 4 就是 2,那么数组第 2 个值加一。
遍历完成后
题目只要求找到一个没出现过的数即可,发现数组下标为 0 的值为 3,没有满 4,说明 0~3 这个范围内一定有数没有出现过,将下一轮搜查的范围限制到 0~3,继续之前的操作
最后可以发现 1 从未出现过