Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >判断一个数是否在40亿个整数中?

判断一个数是否在40亿个整数中?

原创
作者头像
Ritchie
修改于 2023-03-03 09:31:26
修改于 2023-03-03 09:31:26
1.3K00
代码可运行
举报
文章被收录于专栏:Ritchie的专栏Ritchie的专栏
运行总次数:0
代码可运行

最近看到一道经典面试题: 在40亿的unsigned int数据中(乱序),给定一个数字target, 判断该target是否存在于这40亿的数据中?

准备工作: 如下代码随机生成[1, 2147483648)的整数集保存在D盘根目录下a.txt,生成数据(一行一个整数)之后(约占磁盘40G),用代码再统计一下生成的数字有3999999040(嗯?有点奇怪,不是生成40亿的数据吗?怎么是39亿多?鬼知道呢?不过够用了)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void build4BNumberLists() {
		try {
			BufferedWriter bw = new BufferedWriter(new FileWriter("D:/a.txt"));
			int maxValue = 2147483647;
			for (long i = 0; i < 4000000000L; i ++ ) {
				int value = new Random().nextInt(maxValue) + 1;
				System.out.println(value);
				if (i % 1000000 == 0 ) {
					System.out.println("遍历到:" + i);
				}
				bw.write(value + "\n");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void totalNumberLists() {
   try {
      BufferedReader br = new BufferedReader(new FileReader("D:/a.txt"));
      String line;
      long count = 0;
      while((line = br.readLine()) != null) {
         System.out.println(line);
         count ++;
      }
      System.out.println("total number of numbers in this file is:" + count);
   } catch (Exception e) {

   }
}

i. 使用set集合add操作,将40亿的数据一次性加载进内存,然后只需要使用contains方法判断target是否存在即可

问题: 一个unsigned int的元素,需要占4B的空间,按照最坏的打算,40亿个整数不重复,那么需要40亿*4B(1.6 * 10^10B 约等 16GB)的内存,采用这种方法的话,用我自己电脑16G内存跑的话,跑到add 6500w的数据的时候,CPU直接100%,内存就已经飙升到90%了,我还是赶紧关停了程序,缺点就是需要很大的内存

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static boolean findTargetInHashSet(int target) {
		try {
			BufferedReader br = new BufferedReader(new FileReader("D:/a.txt"));
			String line;

			HashSet<Integer> set = new HashSet<>();
			while ((line = br.readLine()) != null) {
				int value = Integer.parseInt(line);
				set.add(value);
				++ count;
				if (count % 1000000 == 0) {
					System.out.println("load the " + count + "th number");
				}
			}
			return set.contains(target);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

ii. 不适用set集合,直接将读过来的数据与target进行比较

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static boolean findTargetInLine(long target) {
		try {
			BufferedReader br = new BufferedReader(new FileReader("D:/a.txt"));
			String line;
			while((line = br.readLine()) != null) {
				long value = (Long.parseLong(line));
				if (value == target) {
					return true;
				}
			}
			return false;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

iii. 使用bitmap算法(引用下维基百科的解释: In computing, a bitmap is a mapping from some domain (for example, a range of integers) to bits. It is also called a bit array or bitmap index. 在计算机中,bitmap是用作某个值(例如: 给定范围的整数),映射为位(bit), 也被叫做位数组或位图)。总而言之,就是一种映射关系,即用一个bit来代表一位unsigned long int,按照正常的空间来算1个unsigned long int占8个bit,8个字节,即64bit。 用1bit可以表示原来64bit的数据,这样就节省64倍的空间。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
举个例子:  给定一个long类型的数组,向其中如下的一些数据,以下是具体的位图展示
long类型是8Byte = 8 * 8 = 64bit, 让每一个位代表一个值,假设这批数字的最大值max = 40亿, 这样我们可以开辟一个
(400000000 / 64 + 1)空间的大小, 数组中每一个long类型的值是64bit, 实际代表了64个long值:
a[0]: 0~63
a[1]: 64~127
.......
arr[62500000]:40亿~40亿+63, 开辟+1个空间大小的原因是下标是从0开始的,不+1的话,无法表示[40亿, 40亿+63]

开辟62500001个空间大小的long类型数组,为了好算,即625000000*8=5*10^8约等于0.5G = 512MB


如下是arr数组中的值,存储为bitmap之后的映射关系如下,存在即1不存在则0:
long[] arr = new long[]{4000000000L, 3999999999L, 3999999998L, 3999999997L, 0, 1, 32, 63, 64, 65, 128, 256, 1999};

a[0][1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
a[1][1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
a[2][1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
a[4][1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
a[31][0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
a[62499999][0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1]
a[62500000][1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

按照如上的原理分析, 写出如下几个函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 开辟一个long数组,限定该代码中
public class BitLongMap {
	
	private long maxSize;
	private long arr[];
	
	public BitLongMap() {
		maxSize = 4000000000L;
		int length = (int)(maxSize / 64) + 1;
		arr = new long[length];
	}
}

add(long value): 将数据集插入到分配的数组中,完成映射

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void add(long value) {
		// 注意这里的1L一定要加L,不加L的话,默认是按照int 32位来处理的,得不到预期的结果
		arr[(int)(value / 64)] |= 1L << (value % 64);
	}

exist(long target): 判断给定的target是否存在于数据集中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean exist(long target) {
		return (arr[(int)(target / 64) ] & (1L << (target % 64))) != 0;
	}

sort(): 给定的数据集进行排序

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void sort(long max) {
		int row = (int)(max / 64L) + 1;
		for (int i = 0; i < row; i ++ ) {
			long value = arr[i];
			for (int j = 0; j < 64; j ++ ) {
				if ((value & (1L << j)) != 0) {
					System.out.print(i * 64L + j + " ");
				}
			}
		}
		System.out.println();
	}

完整代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BitLongMap {
	
	private long maxSize;
	private long arr[];
	
	public BitLongMap() {
		maxSize = 4000000000L;
		int length = (int)(maxSize / 64) + 1;
		arr = new long[length];
	}

	public void add(long value) {
		arr[(int)(value / 64)] |= 1L << (value % 64);
		display(1);
		System.out.println();

	}
	
	public boolean exist(long target) {
		return (arr[(int)(target / 64) ] & (1L << (target % 64))) != 0;
	}
	
	// 打印对应的位信息
	public void display(int row) {
		for (int i = 0; i < row; i ++ ) {
			long value = arr[i];
			if (value != 0) {
				List<Long> ans = new ArrayList<>();
				for (int j = 0; j < 64; j ++ ) {
					ans.add(value & 1);
					value >>= 1;
				}
				System.out.println("a[" + i + "]" + ans.toString());
			}
			
		}
	}
	
	// 61 62 63 64 128 198 1111 1999 19999 2147483647 2147483648 3999999999 4000000000
	public void sort(long max) {
		int row = (int)(max / 64L) + 1;
		for (int i = 0; i < row; i ++ ) {
			long value = arr[i];
			for (int j = 0; j < 64; j ++ ) {
				if ((value & (1L << j)) != 0) {
					System.out.print(i * 64L + j + " ");
				}
			}
		}
		System.out.println();
	}
	
	// 4B 不重复的正整数数中求出中位数
	public void middleNumber() {
		int row = (int)(maxSize / 64L) + 1;
		long count = 0;
		for (int i = 0; i < row; i ++ ) {
			long value = arr[i];
			for (int j = 0; j < 64; j ++ ) {
				if ((value & (1L << j)) != 0) {
					count ++;
				}
			}
		}
		System.out.println(count);
		long index = 0;
		// even
		long leftValue = 0, middleValue = 0, rightValue = 0;
		// index = count / 2, count / 2 - 1 求和 / 2
		for (int i = 0; i < row; i ++ ) {
			long value = arr[i];
			for (int j = 0; j < 64; j ++ ) {
				
				if ((value & (1L << j)) != 0) {
					++ index;
					if (index - 1 == count / 2 - 1) {
						leftValue = i * 64L + j;
					}
					if (index - 1 == count / 2) {
						rightValue = i * 64L + j;
						middleValue = rightValue;
					}
					
					if (index > count / 2) {
						break;
					}
				}
			}
		}
		
		if ((count & 1) == 0) {
			// 不过这里还是要考虑要爆long
			System.out.println((leftValue + rightValue) / 2);
		} else {
			System.out.println(middleValue);
		}
		
	}
	
	
	public static void main(String[] args) {
		BitLongMap bitLongMap = new BitLongMap();
//		long[] arr = new long[]{64, 63, 62, 61, 128, 198, 1111, 19999, 2147483647, 2147483648L, 1999, 3999999999L, 4000000000L};
//		long[] arr = new long[]{64, 63, 62, 61, 128, 198, 1111, 1222, 19999, 2147483647, 2147483648L, 1999, 3999999999L, 4000000000L};
//		long[] arr = new long[]{64, 32, 198};
//		long[] arr = new long[]{4000000000L, 3999999999L, 3999999998L};
		
//		long[] arr = new long[]{4000000000L, 3999999999L, 3999999998L, 3999999997L, 0, 1, 32, 63, 64, 65, 128, 256, 1999};
		long[] arr = new long[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 31, 32, 33, 63};
		for (int i = 0; i < arr.length; i ++ ) {
			bitLongMap.add(arr[i]);
		}
		
//		System.out.println(bitLongMap.exist(65));
//		System.out.println(bitLongMap.exist(64));
//		System.out.println(bitLongMap.exist(63));
//		System.out.println(bitLongMap.exist(60));
//		System.out.println(bitLongMap.exist(55));
//		System.out.println(bitLongMap.exist(32));
//
//		System.out.println(1L << 63);
//		System.out.println(1 << 31);
//		bitLongMap.display((int)(4000000000L / 64) + 1);
//		bitLongMap.sort(4000000000L);
//		bitLongMap.middleNumber();
	}
}

看了网上一些别人的文章,说40亿数据中查找target使用bitmap像是优秀的解法,但是我个人觉得这个bitmap算法在这个场景没什么用?原因是: bitmap虽然节省很多内存,但是为什么一定要把数据一次性加载进内存呢?比如使用上述第二种方法bufferedreader按行读取即可, 时间复杂度也是O(n)

当然我认为bitmap是在如下的场景下会更适用些(请注意题目的约束条件这里只描述了大致意思):

  • 文件中有40亿个互不相同的QQ号码,请设计算法对QQ号码进行排序
  • 文件中有40亿个互不相同的QQ号码,求这些QQ号码的中位数
  • 文件中有40亿个互不相同的QQ号码,求这些QQ号码的top-K

等等......

最后为了更好的学习bitmap的相关算法,JDK中提供了BitSet类以及Google的EWAHCompressedBitmap都有具体的源代码参考,创作不易,点个赞再走~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
稀疏数组和队列
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
用户9615083
2022/12/30
3980
稀疏数组和队列
我用Java几分钟处理完30亿个数据...
点击上方蓝色字体,选择“设为星标” 回复”学习资料“获取学习宝典 文章来源:https://c1n.cn/GM8hb 目录 场景说明 模拟数据 场景分析 读取数据 处理数据 遇到的问题 场景说明 现有一个 10G 文件的数据,里面包含了 18-70 之间的整数,分别表示 18-70 岁的人群数量统计,假设年龄范围分布均匀,分别表示系统中所有用户的年龄数,找出重复次数最多的那个数,现有一台内存为 4G、2 核 CPU 的电脑,请写一个算法实现。         23,31,42,19,60,30,36,
猿天地
2022/05/16
5000
我用Java几分钟处理完30亿个数据...
40亿个QQ号,限制1G内存,如何去重?
4*4000000000 /1024/1024/1024 = 14.9G ,考虑到其中有一些重复的话,那1G的空间也基本上是不够用的。
程序员大彬
2023/09/06
3460
40亿个QQ号,限制1G内存,如何去重?
Java底层知识总结-0
并发:同时拥有两个或者多个线程,如果程序在单核处理器上运行多个线程将交替的换出或者换入内存,这些线程是同时存在的。每个线程都会处于执行过程中的某个状态,如果运行在多核处理器上,程序中的每个线程都将会分配到一个处理器核,因此可以同时运行。
用户2032165
2019/03/04
8540
Java底层知识总结-0
场景题:假设有40亿QQ号,但只有1G内存,如何实现去重?
当数据量比较大时,使用常规的方式来判重就不行了。例如,使用 MySQL 数据库判重,或使用 List.contains() 或 Set.contains() 判重就不行了,因为数据量太大会导致内存放不下,或查询速度太慢等问题。
磊哥
2025/01/08
1110
场景题:假设有40亿QQ号,但只有1G内存,如何实现去重?
面试题: 一个单调递增的数组 随机拿出一个数 你怎么找到这个数
就以 1,2,3,4,5,6,7,8,9... 100为例吧 小强把88这个数拿了出来 我怎么能很快找到?
木子的昼夜
2021/04/05
4030
面试题: 一个单调递增的数组 随机拿出一个数  你怎么找到这个数
✅上亿数据,限制1G内存,如何去重?
有许多方法可以用来去重,比如使用列表、集合等等,但这些方法通常只适用于一般情况。然而,当涉及到大量数据去重时,常见的 Java Set、List,甚至是 Java 8 的新特性 Stream 流等方式就显得不太合适了。在处理大量数据的需求场景下,我们不得不提及 BitMap。
@派大星
2024/03/04
3860
从一道高大上的面试题来学习位图算法BitMap
今天我偶然刷到了一篇文章,“华为二面:一个文件里面有5亿个数据,一行一个,没有重复的,进行排序”。不知道又是哪个无良媒体瞎起的标题,夺人眼球。
秃头哥编程
2021/07/14
9620
从一道高大上的面试题来学习位图算法BitMap
位运算操作[Java语言描述]
阅读本文之前,务必搞清楚计算机中有关源码,补码的相关概念,位运算 & (按位与) | (按位或) ~ (取反) ^ (异或)相关概念和操作
开胃狼
2019/11/18
1.2K0
位运算操作[Java语言描述]
海量数据处理之bitmap
本文将讲述Bit-Map算法的相关原理,Bit-Map算法的一些利用场景,例如BitMap解决海量数据寻找重复、判断个别元素是否在海量数据当中等问题.最后说说BitMap的特点已经在各个场景的使用性。
Spark学习技巧
2018/12/13
1.3K0
海量数据处理之bitmap
如何用 Java 几分钟处理完 30 亿个数据?
现有一个 10G 文件的数据,里面包含了 18-70 之间的整数,分别表示 18-70 岁的人群数量统计,假设年龄范围分布均匀,分别表示系统中所有用户的年龄数,找出重复次数最多的那个数,现有一台内存为 4G、2 核 CPU 的电脑,请写一个算法实现。
用户1220090
2025/03/24
540
如何用 Java 几分钟处理完 30 亿个数据?
挑战程序竞赛系列(52):4.2 Nim 与 Grundy 数
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014688145/article/details/77863352
用户1147447
2019/05/26
6360
java | 深入理解Java枚举类型(三)
blog.csdn.net/javazejian/article/details/71333103
JavaFish
2019/10/17
9590
POJ 刷题系列:2965. The Pilots Brothers' refrigerator
根据给定的文章内容,撰写摘要总结。
用户1147447
2018/01/02
4840
POJ 刷题系列:2965. The Pilots Brothers' refrigerator
Storm(三)Java编写第一个本地模式demo
本地模式 在本地模式下,Storm拓扑结构运行在本地计算机的单一JVM进程上。这个模式用于开发、测试以及调试,因为这是观察所有组件如何协同工作的最简单方法。在这种模式下,我们可以调整参数,观察我们的拓扑结构如何在不同的Storm配置环境下运行。要在本地模式下运行,我们要下载Storm开发依赖,以便用来开发并测试我们的拓扑结构。我们创建了第一个Storm工程以后,很快就会明白如何使用本地模式了。 NOTE: 在本地模式下,跟在集群环境运行很像。不过很有必要确认一下所有组件都是线程安全的,因为当把它们部署到远程模式时它们可能会运行在不同的JVM进程甚至不同的物理机上,这个时候它们之间没有直接的通讯或共享内存。
stys35
2019/03/05
1.1K0
如何判断一个数是否在 40 亿个整数中?
小史是一个应届生,虽然学的是电子专业,但是自己业余时间看了很多互联网与编程方面的书,一心想进BAT。
五分钟学算法
2019/07/10
8630
如何判断一个数是否在 40 亿个整数中?
【BAT面试必会】如何在10亿数中找出前1000大的数
小史:我可以用分治法,这有点类似快排中partition的操作。随机选一个数t,然后对整个数组进行partition,会得到两部分,前一部分的数都大于t,后一部分的数都小于t。
乔戈里
2019/02/26
5390
4.7字符串匹配(3)
挑战程序竞赛系列(68):4.7字符串匹配(3) 题意: 找茬:从大图中找出特定方块小图,旋转翻转皆可。 先编码,后hash计算,最后匹配输出结果,和书上一个思路。 代码如下: import j
用户1147447
2019/05/26
3720
【面试现场】如何判断一个数是否在40亿个整数中?
小史是一个应届生,虽然学的是电子专业,但是自己业余时间看了很多互联网与编程方面的书,一心想进BAT。
帅地
2019/06/06
6700
【面试现场】如何判断一个数是否在40亿个整数中?
在20亿个随机整数中找出m是否存在,你打算怎么存数据呢?
按照惯例,用int存储数据的话,在Java中,int占4字节,1字节=8位(1 byte = 8 bit),一共20亿个int,因而占用的空间约(2000000000*4/1024/1024/1024)≈7.45G
一条coding
2021/08/12
7060
在20亿个随机整数中找出m是否存在,你打算怎么存数据呢?
推荐阅读
相关推荐
稀疏数组和队列
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验