如何在有序数剧中找到缺失的数字(例如1-100,随机抽出2个数字,找出抽取的数)

前言

今天无意看到一个面试题,觉的很有意思。
在这里插入图片描述
我理解的就是有100w条数据,从中抽走2条,然后问抽走的是哪两个。一开始自己也想了许多,什么二分法了,循环前后数相减不等于2的就是删除的数据。不过很快自己就推翻自己了,如果人家是拿走相邻的两个呢?如果人家不拿走2个,拿走5个6个呢?一开始也是自己想了很多很多,不过觉的都很烂。直到我发现了 java.util 里的 BitSet 类。我去很好的一次性的,解决了这个问题,话不多说先上代码。

代码操作

 public static void main(String[] args) {
        //获得系统的时间,单位为毫秒
        long startTime = System.currentTimeMillis();
        //造出100w条数据
        List<Integer> initLists = new ArrayList<>();
        for (int i = 1; i <= 1000000; i++) {
            initLists.add(i);
        }
        //随机在100w里抽取5条
        Random random = new Random();
        List<Integer> randomLists = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            randomLists.add(random.nextInt(1000000));
        }
        System.out.println("我是随机剔除数" + randomLists);
        //100w里去除这5条
        for (Integer rand : randomLists) {
            initLists.remove(rand);
        }
        //获取被去除的5个数
        List<Integer> removeNumbers = findRemoveNumber1(initLists, 1000000);
        System.out.println("被剔除数" + randomLists);
        long endTime = System.currentTimeMillis();    //获取结束时间
        System.out.println("程序运行时间" + (endTime - startTime) + "毫秒");
    }

    /**
     * 找出剔除数.
     *
     * @param numbers 现在所剩数据
     * @param count   原先数据总数
     * @return
     */
    private static List<Integer> findRemoveNumber1(List<Integer> numbers, int count) {
        List<Integer> removeList = new ArrayList<>();
        //将现有数加入BitSet
        BitSet bitSet = new BitSet(count);
        for (Integer number : numbers) {
            bitSet.set(number);
        }
        //遍历查找
        for (int i = 1; i <= count; i++) {
            if (!bitSet.get(i)) {
                removeList.add(i);
            }
        }
        return removeList;
    }

    /**
     * 找出剔除数.
     *
     * @param numbers 现在所剩数据
     * @param count   原先数据总数
     * @return
     */
    private static List<Integer> findRemoveNumber2(List<Integer> numbers, int count) {
        List<Integer> removeList = new ArrayList<>();
        //获取剔除的数量
        int removeCount = count - numbers.size();
        //将现有数加入BitSet
        BitSet bitSet = new BitSet(count);
        for (Integer number : numbers) {
            bitSet.set(number - 1);
        }
        //定义
        int lastRemoveIndex = 0;
        for (int i = 1; i <= removeCount; i++) {
            lastRemoveIndex = bitSet.nextClearBit(lastRemoveIndex);
            removeList.add(++lastRemoveIndex);
        }
        return removeList;
    }

方法1
在这里插入图片描述
方法2
在这里插入图片描述
两个方法的运行时间基本没有差别。

重点 BitSet 类

  • BitSet的原理

Java BitSet可以按位存储,计算机中一个字节(byte)占8位(bit);

而BitSet是位操作的对象,值只有0或1(即true 和 false),内部维护一个long数组,初始化只有一个long segement,所以BitSet最小的size是64;随着存储的元素越来越多,BitSet内部会自动扩充,一次扩充64位,最终内部是由N个long segement 来存储;

默认情况下,BitSet所有位都是0即false;

正如上述方案来说:

皇冠人群是一个BitSet,其中1\3\5\63\65\67\69\127对应位为1;即橙色部分;

活跃人群也是一个BitSet,其中5\65\68\127对应位为1;即橙色部分;

而64个位为一个long数组,因此64对应的位就被分配到第2个long数组;

  • BitSet的应用场景

海量数据去重、排序、压缩存储
常见的应用是那些需要对海量数据进行一些统计工作的时候,比如日志分析、用户数统计等等
如统计40亿个数据中没有出现的数据,将40亿个不同数据进行排序等。
现在有1千万个随机数,随机数的范围在1到1亿之间。现在要求写出一种算法,将1到1亿之间没有在随机数中的数求出来

  • BitSet的基本操作

and(与)、or(或)、xor(异或)

  • BitSet的优缺点

优点:

l 按位存储,内存占用空间小

l 丰富的api操作

缺点:

l 线程不安全

l BitSet内部动态扩展long型数组,若数据稀疏会占用较大的内存

  • BitSet为什么选择long型数组作为内部存储结构

JDK选择long数组作为BitSet的内部存储结构是出于性能的考虑,在and和or的时候减少循环次数,提高性能;

因为BitSet提供and和or这种操作,需要对两个BitSet中的所有bit位做and或者or,实现的时候需要遍历所有的数组元素。使用long能够使得循环的次数降到最低,所以Java选择使用long数组作为BitSet的内部存储结构。

举个例子:

当我们进行BitSet中的and, or, xor操作时,要对整个bitset中的bit都进行操作,需要依次读出bitset中所有的word,如果是long数组存储,我们可以每次读入64个bit,而int数组存储时,只能每次读入32个bit。

  • BitSet源码解析

参考JunitTest断点查看代码,了解BitSet每个方法的实现逻辑

附:

源码解析博文:http://www.cnblogs.com/lqminn/archive/2012/08/30/2664122.html

Java移位基础知识:https://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html

简单理解:BitSet是位操作的对象,值只有0或1即false和true,内部维护了一个long数组,初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充,最终内部是由N个long来存储,这些针对操作都是透明的。
用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用用的时候既可根据某一个是否为0表示,此数是否出现过。
当我们new BitSte(10) 时 里面默认都是false,当我们set(6) 时 6的位置 就变成了true;

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值