位图法:判断一个数是否在40亿个整数中?

简介: 位图法:判断一个数是否在40亿个整数中?

题目


最近看到一个题目:给40亿个不重复的 unsigned int 的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?

 

解法


搜了一下资料,该题目是腾讯的一道面试题,目前网上普遍给出的答案有两种。


1.《编程珠玑》给出的方案


我们把40亿个数中的每一个用32位的二进制来表示,假设这40亿个数开始放在一个文件中。


然后将这40亿个数分成两类:1.最高位为02.最高位为1


并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿(这相当于折半了);


与要查找的数的最高位比较并接着进入相应的文件再查找

再然后把这个文件为又分成两类:1.次最高位为02.次最高位为1


并将这两类分别写入到两个文件中,其中一个文件中数的个数<=10亿,而另一个>=10亿(这相当于折半了);与要查找的数的次最高位比较并接着进入相应的文件再查找。

.......

以此类推,就可以找到了,而且时间复杂度为O(logn)

 

此方案不是本文要讲的重点,只是把思路放在这边供大家参考。

 

2.位图


思路


我们之所以无法将40亿个数字直接读取到内存去进行处理,是因为40亿个 unsigned int 的整数大约要占15G内存,正常情况下,没有这么大的内存,也不可能这样做。


这种情况是因为一个整数占用了4个字节(Byte),而在本题中,我们其实只关心某个数字是否存在,在这种情况下,我们可以通过位图法来解决该问题。


位图法思想:对于40亿个 unsigned int 的整数,每个数字用1个二进制数(一个二进制数占用1Bit1Byte = 8Bit)来表示该数字是否存在,0为不存在,1为存在。从低位开始数:


1个二进制数表示整数0是否存在,

2个二进制数表示整数1是否存在,

3个二进制数表示整数2是否存在,

依次类推... ...


4294967296个二进制数用于表示整数4294967295是否存在。

unsigned int 32&64位编译器的范围为 042949672954294967296个二进制数大约占用512M内存,是一个可以接受的范围。

 

例子


题目:我们读取了3个整数:124,要判断整数3是否被读取过了。


解答过程:


1)对于40亿个 unsigned int 的整数,我们可以定义4294967296个二进制数,并且全部初始化值为0

2)读取整数1:将第2个二进制数赋值为1,表示整数1是存在的,此时值为:10(高位还有42949672940,为了方便阅读不写出来,下文同)

3)读取整数2:将第3个二进制数赋值为1,表示整数2是存在的,此时值为:110

4)读取整数4:将第5个二进制数赋值为1,表示整数4是存在的,此时值为:1 0110

5)判断整数3是否被读取过,因为第1个二进制数表示整数0是否存在,因此整数3则通过第4个二进制数表示,此时的值为:1 0110,第4个二进制数为0,所以得出结论:整数3没有被读取过。

 

代码实现


由于Java中无法直接操作二进制数,因此我们可以通过 int 来实现。1个二进制数占用1 Bit1int 占用4 Byte,也就是32 Bit。因此,我们可以使用1int来表示32个二进制数。

所以,我们有以下思路:

1int表示:整数0 ~ 31是否存在,

2int表示:整数32 ~ 63是否存在,

3int表示:整数64 ~ 95是否存在,依此类推。

因此,我们最终可以使用一个int数组来表示4294967296个二进制数,通过数组的下标来指示第几个int


代码如下


package com.joonwhee.open.leetcode.temp;
/**
 * 位图法
 *
 * @author joonwhee
 * @date 2019/1/22
 */
public class BitMap {
    /**
     * 位图提供的最大长度,
     * 比如unsigned int的最大值为4294967295, 则需要的length为4294967296
     */
    private long length;
    /**
     * 位图桶
     */
    private static int[] bitmapBucket;
    /**
     * int用来表示32位二进制数,
     * BIT_VALUE[0]表示第1个二进制数存在、
     * BIT_VALUE[1]表示第2个二进制数存在,以此类推
     *
     * BIT_VALUE[0] = 00000000 00000000 00000000 00000001
     * BIT_VALUE[1] = 00000000 00000000 00000000 00000010
     * BIT_VALUE[2] = 00000000 00000000 00000000 00000100
     * ...
     * BIT_VALUE[31] = 10000000 00000000 00000000 00000000
     */
    private static final int[] BIT_VALUE = {
            0x00000001, 0x00000002, 0x00000004, 0x00000008,
            0x00000010, 0x00000020, 0x00000040, 0x00000080,
            0x00000100, 0x00000200, 0x00000400, 0x00000800,
            0x00001000, 0x00002000, 0x00004000, 0x00008000,
            0x00010000, 0x00020000, 0x00040000, 0x00080000,
            0x00100000, 0x00200000, 0x00400000, 0x00800000,
            0x01000000, 0x02000000, 0x04000000, 0x08000000,
            0x10000000, 0x20000000, 0x40000000, 0x80000000};
    /**
     * length为1 - 32: 需要1个桶
     * length为33 - 64: 需要2个桶
     * ...
     * 以此类推
     *
     * @param length
     */
    public BitMap(long length) {
        this.length = length;
        // 根据长度算出,所需位图桶个数
        bitmapBucket = new int[(int) (length >> 5) + ((length & 31) > 0 ? 1 : 0)];
    }
    /**
     * 查找number是否存在于位图桶中
     *
     * @param number 要查询的数字
     * @return true: number在位图桶中, false: number不在位图桶中
     */
    public boolean getBit(long number) {
        if (number < 0 || number > length) {
            throw new IllegalArgumentException("非法参数");
        }
        // 计算该number在哪个桶
        int belowIndex = (int) (number >> 5);
        // 求出该number在桶里的下标,(求出该值在32位中的哪一位, 下标0 - 31)
        int offset = (int) (number & 31);
        // 拿到该桶的值
        int currentValue = bitmapBucket[belowIndex];
        // 计算该number是否存在
        return ((currentValue & BIT_VALUE[offset])) == 0 ? false : true;
    }
    /**
     * 将number在位图桶中标记为存在
     *
     * @param number 要标记的数字
     */
    public void setBit(long number) {
        // 合法性校验
        if (number < 0 || number >= length) {
            throw new IllegalArgumentException("非法参数");
        }
        // 计算该number在哪个桶
        int belowIndex = (int) (number >> 5);
        // 求出该number在桶里的下标,(求出该值在32位中的哪一位, 下标0 - 31)
        int offset = (int) (number & 31);
        // 拿到该桶的当前值
        int currentValue = bitmapBucket[belowIndex];
        // 将number在桶里标记
        bitmapBucket[belowIndex] = currentValue | BIT_VALUE[offset];
    }
    public static void main(String[] args) {
        BitMap bitMap = new BitMap(4294967296L);
        bitMap.setBit(4294967295L);
        System.out.println(bitMap.getBit(4294967295L));
        System.out.println(bitMap.getBit(4294967294L));
    }
}

了解了思路后,代码就比较简单了。

1)通过构造函数传的 length 值,构建一个足够大的 int数组bitmapBucket,对于题目的unsigned int 的整数,length4294967296刚好够用。

2)将40亿个给定的数通过 setBit 方法,将对应的位置的二进制数标记为11代表存在)。

3)通过 getBit 方法判断给定的 number 是否存在于之前的40亿个数中。

 

setBit 例子:例如我们要将整数 32 标记为存在。


1)首先计算出整数 32 所在的位图桶下标为1,也就是bitmapBucket[1]

2)接着计算出整数 32 bitmapBucket[1] 桶中的下标0(下标0代表该桶的第1个二进制数)。

3)拿到 bitmapBucket[1] 当前值 currentValue

4)最后我们要将 bitmapBucket[1] 桶中第1个二进制数标记为1,并且不影响之前已经标记的二进制数,因此将 currentValue 0000 0000 0000 0000 0000 0000 0000 0001 进行运算即可。而对于每一个二进制数,我们通过BIT_VALUE来表示,这边的0000 0000 0000 0000 0000 0000 0000 0001 ,可以通过 BIT_VALUE[0] 来表示。

相关文章
|
7天前
读取7个数(1—50)的整数值
【10月更文挑战第10天】读取7个数(1—50)的整数值。
20 4
|
2月前
|
存储 算法 程序员
极限挑战:40亿个非负整数中找到没有出现的数(bit数组)
大家好!我是小米,一名热衷于技术分享的29岁程序员。今天探讨的问题是在40亿个非负整数中找到未出现的数字。直接使用哈希表因内存限制而不可行。本文提出了一种解决方案——利用bit数组。通过标记出现过的数字,最终找出未被标记的位置所对应的数字即为答案。对于更严格的内存限制(如10MB),文章还介绍了分块处理的方法,先统计每个区间的数字数量,找到计数不足的区间后再精确处理。这种算法不仅展示了巧妙利用有限资源的能力,也为实际工程问题提供了解决思路。希望各位读者能从中受益,享受编程带来的乐趣!
52 15
|
5月前
1004.最大连续1的个数
1004.最大连续1的个数
29 0
|
5月前
|
算法
算法题 — 整数转二进制,查找其中1的数量
算法题 — 整数转二进制,查找其中1的数量
41 0
|
5月前
|
Python
计算小于或等于n的非负整数区间包含的1的数量
计算小于或等于n的非负整数区间包含的1的数量
53 0
|
5月前
|
存储 机器学习/深度学习 C语言
Day3 字符串中找出连续最长的数字串、数组中出现次数超过一半的数字
Day3 字符串中找出连续最长的数字串、数组中出现次数超过一半的数字
53 0
|
10月前
统计两个整数所对应的二进制数中的不同位数的个数
统计两个整数所对应的二进制数中的不同位数的个数
37 0
打印3的倍数的数
打印3的倍数的数
68 0
求一个整数储存在内存中的二进制1的个数
求一个整数储存在内存中的二进制1的个数