算法通关村——位运算在查找重复元素中的妙用

海量数据中,此时普通的数组、链表、Hash、树等等结构有无效了,因为内存空间放不下了。而常规的递归、排序、回溯、贪心和动态规划等思想也无效了,因为执行都会超时,必须另外想办法。这类问题该如何下手呢?这里介绍三种非常典型的思路:

  1. 使用位存储,使用位存储最大的好处是占用的空间是简单存整数的1/8。例如一个40亿的整数数组,如果用整数存储需要16GB左右的空间,而如果使用位存储,就可以用2GB的空间,这样很多问题就能够解决了。
  2. 如果文件实在太大,无法在内存中放下,则需要考虑将大文件分成若干小块,先处理每个块,最后再逐步得到想要的结果,这种方式也叫做外部排序。这样需要遍历全部序列至少两次,是典型的用时间换空间的方法。
  3. 堆,如果在超大数据中找第K大、第K小,K个最大、K个最小,则特别适合使用堆来做。而且将超大数据换成流数据也可以,而且几乎是唯一的方式,口决就是“查小用大堆,查大用小堆”。

我们这里来看一道比较简单的例题

用4KB内存寻找重复元素

题目要求:给定一个数组,包含从1到N的整数,N最大为32000,数组可能还有重复值,且N的取值不定,若只有4KB的内存可用,该如何打印数组中所有重复元素。

分析:本身是一道海量数据问题的热身题,如果去掉“只有4KB”的要求,我们可以先创建一个大小为N的数组,然后将这些数据放进来,但是整数最大为32000。如果直接采用数组存,则应该需要
32000*4B=128KB的空间,而题目有4KB的内存限制,我们就必须先解决该如何存放的问题。

如果只有4KB的空间,那么只能寻址842^10个比特,这个值比32000要大的,因比我们可以创建32000比特的位向量(比特数组),其中一个比特位置就代表一个整数。

利用这个位向量,就可以遍历访问整个数组。如果发现数组元素是V,那么就将位置为的设置为1,碰到重复元素,就输出一下。

下面是代码实现:

public class FindDuplicatesIn32000 {
    // 定义一个用于查找数组中重复项的方法
    public void checkDuplicates(int[] array) {
        // 创建一个位图(BitSet)对象,用于标记已经出现的数字
        BitSet bs = new BitSet(32000);
        
        // 遍历输入的整数数组
        for (int i = 0; i < array.length; i++) {
            int num = array[i]; // 获取当前数组元素的值
            int num0 = num - 1; // 数组的索引从0开始,将当前元素减1以匹配位图索引
            
            // 检查位图中的指定索引位置是否已经标记为true(已经出现过)
            if (bs.get(num0)) {
                // 如果已经标记过,表示当前元素是重复的,将其打印出来
                System.out.println(num);
            } else {
                // 如果没有标记过,将位图中的对应索引位置标记为true(出现过)
                bs.set(num0);
            }
        }
    }
    
    // 定义一个位图(BitSet)类,用于处理位图相关操作
    class BitSet {
        int[] bitset; // 用整数数组表示位图
        
        // 构造函数,创建指定大小的位图
        public BitSet(int size) {
            // 通过右移5位(相当于除以32)来确定整数数组的大小
            this.bitset = new int[size >> 5];
        }
        
        // 获取位图中指定位置的位值(是否为1)
        boolean get(int pos) {
            // 通过右移5位(相当于除以32)来确定整数数组的索引
            int wordNumber = (pos >> 5);// 即wordNumber是确定储存于哪个下标的数组元素
            // 通过与操作(&)来获取指定位的值 (可以理解为对32取余)
            // bitNumber是确定存储在对应数组的32位比特哪个位置上
            int bitNumber = (pos & 0x1F); // 使用0x1F(二进制为11111)来确保位号在0-31之间
           
            // 返回位值是否为1
            return (bitset[wordNumber] & (1 << bitNumber)) != 0;
        }
        
        // 设置位图中指定位置的位值为1
        void set(int pos) {
            // 通过右移5位(相当于除以32)来确定整数数组的索引
            int wordNumber = (pos >> 5);
            // 通过与操作(&)来确定位号
            int bitNumber = (pos & 0x1F); // 使用0x1F(二进制为11111)来确保位号在0-31之间
            
            // 使用按位或操作(|)将指定位置的位值设置为1
            bitset[wordNumber] |= 1 << bitNumber;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值