目录
- 题目:用4KB内存寻找重复元素
-
- 思路分析:使用位存储
- [复杂度:时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)](#复杂度:时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1))
- Go代码
在
海量数据
中,此时普通的数组、链表、Hash、树等等结构有无效了 ,因为内存空间放不下了。而常规的递归、排序,回溯、贪心和动态规划等思想也无效了,因为执行都会超时,必须另外想办法。这类问题该如何下手呢?这里介绍三种非常典型的思路:
使用位存储
,使用位存储最大的好处是占用的空间是简单存整数的1/8。例如一个40亿的整数数组,如果用整数存储需要16GB左右的空间,而如果使用位存储,就可以用2GB的空间,这样很多问题就能够解决了。如果文件实在太大 ,无法在内存中放下,则需要考虑将大文件分成若干小块,先处理每个块,最后再逐步得到想要的结果 ,这种方式也叫做
外部排序
。这样需要遍历全部序列至少两次,是典型的用时间换空间的方法。
堆
,如果在超大数据中找第K大、第K小,K个最大、K个最小,则特别适合使用堆来做。而且将超大数据换成流数据也可以,而且几乎是唯一的方式,口诀就是"查小用大堆,查大用小堆"。
题目:用4KB内存寻找重复元素
给定一个数组,包含从1到N的整数,N最大为32000,数组可能还有重复值,且N的取值不定,若只有4KB的内存可用,该如何打印数组中所有重复元素。
思路分析:使用位存储
如何存储这32000个整数?
常规思路分析:32000个整数,整数用int表示,一个int占用4个字节(byte)
,32000个整数所需内存就是 :
txt
32000 * 4 = 128000(byte)
32000 * 4 / 1024 = 125(KB)
125(KB) > 4(KB) //可见,已经超过题目要求的4KB内存要求。
下面我们使用位存储
的方式:1个字节(byte)=8位(bit)
,32000个正数用32000个位就是:
txt
32000 / 8 = 4000(byte)
32000 / 8 / 1024 = 3.9(KB)
3.9(KB)< 4(KB) //如此,就满足题意,使用了4KB就能存储32000个元素
每个整数对应在位图中的存储状态举例
txt
原数据: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ...
该值在位图中的索引值: 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 ...
该值在位图中的偏移量: 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 ...
1 对应的位图值,和二进制值为:byteMap[0] 00000010
2 对应的位图值,和二进制值为:byteMap[0] 00000100
3 对应的位图值,和二进制值为:byteMap[0] 00001000
4 对应的位图值,和二进制值为:byteMap[0] 00010000
5 对应的位图值,和二进制值为:byteMap[0] 00100000
6 对应的位图值,和二进制值为:byteMap[0] 01000000
7 对应的位图值,和二进制值为:byteMap[0] 10000000
8 对应的位图值,和二进制值为:byteMap[1] 00000001
9 对应的位图值,和二进制值为:byteMap[1] 00000010
...
如何判断是重复的?
既然我们用一个位(bit)代表一个数值,那么该位的两种状态0或1,就可以用于判断该值是否存在。
例如:字节00001101
表示以下情况:
- 第 0 位(最低位)为 1,表示数字 1 出现过。
- 第 1 位为 0,表示数字 2 没有出现过。
- 第 2 位为 1,表示数字 3 出现过。
- 第 3 位为 1,表示数字 4 出现过。
- 后续位为 0,表示数字 5 到 8 都没有出现过。
txt
mark := 1 << offset //offset 就是偏移量
if (bitmap[index] & mask) != 0 {
// 位已经被设置,说明数字出现过
}
bitmap[index] |= mask //设置该位值为1
具体的步骤
位图(Bitmap)是一种数据结构,用于表示一组元素的状态或属性,通常用二进制位来表示,每个位代表一种状态或属性。在计算机科学中,位图被广泛用于各种应用,如图像处理、数据压缩、数据库索引等。
- 初始化位图:由于N最大是32000,可以是哦用一个长度为32000/8=4000的位图,每个位可以表示一个整数。
- 遍历数组,对于数组中的每个元素:
- 计算x在位图中的索引和位偏移。例如:x=5,则索引为5/8=0,位偏移为5%8=5。
- 检查位图的索引位置是否已经被标记。
- 如果未被标记,则将其标记为已访问;
- 如果已经被标记,则说明x是重复的,打印x。
- 打印重复元素。
复杂度:时间复杂度 O ( n ) O(n) O(n)、空间复杂度 O ( 1 ) O(1) O(1)
Go代码
源码地址: GitHub-golang版本(含单元测试代码)
go
func FindDuplicatesIn32000(arr []int) (duplicate []int) {
N := 32000
bitmap := make([]byte, N/8+1)
for _, num := range arr {
// 计算 num 在 bitmap 中的索引
// index := num / 8
index := num >> 3
// 计算 num 在 bitmap 中的偏移量
offset := num % 8
mark := byte(1 << offset)
if bitmap[index]&mark != 0 {
duplicate = append(duplicate, num)
} else {
bitmap[index] |= mark
}
}
return
}
或者
go
func FindDuplicatesIn32000(arr []int) (duplicate []int) {
N := 32000
// 或者这里不用+1,只要索引是base0的即可
bitmap := make([]int, N/32)
for _, num := range arr {
num0 := num - 1 //base0开始
index := num0 / 32
offset := num0 % 32
mark := 1 << offset
if bitmap[index]&mark != 0 {
duplicate = append(duplicate, num)
} else {
bitmap[index] |= mark
}
}
return
}