[Go版]算法通关村第十五关白银——海量数据场景下的热门算法题

目录

  • 题目:从 40个亿 中产生一个不存在的整数
    • 要求:假设你有 1GB 的内存来完成这项任务
      • Go代码
    • 进阶:如果只有 10MB 的内存可用,该怎么办?
      • 1. 考虑10MB的内存,用位图存储的数据容量有多少?
      • 2. 考虑40亿数据,每块用10MB内存,需要多少块?
        • 补充:建议将划分块数取位2的整数倍的原因
      • 3. 明确分成多少块后,接着怎么做?
        • 1. 找出不存在的整数在哪个块中
        • 2. 明确了所在块,找出不存在的值即可
      • Go代码
  • 题目:用 2GB 内存在 20亿 个整数中找到出现次数最多的数
    • 哈希表统计
      • 确定存储结构的数据类型(考虑值溢出问题)
    • 哈希函数分割文件(考虑 2GB 内存能存储多少个值)
      • 确保将相同的数分配到同一个小文件中
    • 小文件中的统计
    • 寻找每个小文件的第一名
    • 从16个小文件中选出全局第一名
  • 题目:从 100亿 个URL中查找的问题
    • 要求:有一个包含 100 亿个 URL 的大文件,假设每个 URL 占用 64B,请找出其中所有重复的 URL。
    • 补充问题:某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门 Top 100 词汇的可行办法。
    • 总结处理大规模数据的技巧方法:哈希分流、哈希表、堆排序、位图
  • 题目:40 亿个非负整数中找到出现两次的数
    • 要求:32 位无符号整数的范围是 0~4 294 967 295,现在有 40 亿个无符号整数,可以使用最多 1GB 的内存,找出所有出现了两次的数。
    • Go代码

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

  1. 使用位存储,使用位存储最大的好处是占用的空间是简单存整数的1/8。例如一个40亿的整数数组,如果用整数存储需要16GB左右的空间,而如果使用位存储,就可以用2GB的空间,这样很多问题就能够解决了。

  2. 如果文件实在太大 ,无法在内存中放下,则需要考虑将大文件分成若干小块,先处理每个块,最后再逐步得到想要的结果,这种方式也叫做外部排序。这样需要遍历全部序列至少两次,是典型的用时间换空间的方法

  3. 如果在超大数据中找第K大、第K小,K个最大、K个最小,则特别适合使用堆来做。而且将超大数据换成流数据也可以,而且几乎是唯一的方式,口诀就是“查小用大堆,查大用小堆”。

题目:从 40个亿 中产生一个不存在的整数

要求:假设你有 1GB 的内存来完成这项任务

对于40亿数据的存储所需内存分析:

//直接存储 需要15GB,不满足要求
4000000000*4/1024/1024/1024 ≈ 15GB

//使用位图存储 需要0.5GB,满足要求
4000000000/8/1024/1024/1024 ≈ 0.5GB

所以,跟上一节使用的位存储方案一样即可,话不多说,直接上代码

Go代码

源码地址: GitHub-golang版本(含单元测试代码)

func FindNoExistNumBy1G(arr []int) int {
	N := 4000000000
	bitmap := make([]int, N/32+1)
	for _, num := range arr {
		num0 := num - 1
		index := num0 / 32
		offset := num0 % 32
		mark := 1 << offset
		bitmap[index] |= mark
	}
	for index, v := range bitmap {
		for i := 0; i < 32; i++ {
			mark := 1 << i
			if v&mark == 0 {
				return index*32 + i + 1
			}
		}
	}
	return -1
}

进阶:如果只有 10MB 的内存可用,该怎么办?

1. 考虑10MB的内存,用位图存储的数据容量有多少?

10*1024*1024*8 = 83886080(bit)

2. 考虑40亿数据,每块用10MB内存,需要多少块?

对40亿数据,使用分块处理,如果每块用10MB内存使用位图存储,只需 4000000000/83886080 ≈ 48 块 即可。

而一般来说,我们划分都是使用2的整数倍,因此这里划分成64块是更合理的。

2^2=4
2^3=8
2^4=16
2^5=32
2^6=64
2^26=67108864
2^32=4294967296

补充:建议将划分块数取位2的整数倍的原因

  1. 容错性: 通过将划分块数取为 2 的整数倍,你可以更好地应对数据量的增长和变化。如果未来需要处理更大的数据集,你不必重新设计划分块的大小,只需调整块数即可。
  2. 适应性: 以 2 的整数倍作为划分块数,更适应于计算机的内存和硬件存储结构。计算机通常更容易处理 2 的幂次方大小的数据,因为这与内存、缓存和磁盘的操作单位相匹配。
  3. 性能平衡: 在外部排序中,划分块的大小和块数之间存在一个平衡。虽然划分更多的块可能会在每块内有更少的数据,但过多的块会增加排序和合并的开销。选择适度的划分块数可以在时间和空间效率之间取得平衡。

3. 明确分成多少块后,接着怎么做?

接上面,这里40亿数据,我们分成了64块来处理,即 countArr := make([]int, 64)

1. 找出不存在的整数在哪个块中

遍历40亿数据,明确每个数据对应的块,统计落在每个块上的总数量,然后判断哪个块的总数<平均数,则不存在的整数在哪个块中。(这里一定会有至少一个块的总数<平均数)

那每块的平均数是多少?

  • 第一种方法:可以使用数据总数 40亿 除以块数,即 4000000000 / 64 = 62500000
  • 第二种方法:也可以使用 2^32=4294967296(大于40亿) 除以块数 即 4294967296 / 64 = 67108864

举例使用第二种方法:将 4294967296 / 64 = 67108864 得到每块平均存放的个数,即

第  0  块 :67108864 * 0  ~ (67108864 * (0 + 1) - 1)  <==> 0 		 ~ 67108863
第  1  块 :67108864 * 1  ~ (67108864 * (1 + 1) - 1)  <==> 67108864 	 ~ 134217727
第  x  块 :67108864 * x  ~ (67108864 * (x + 1) - 1) 
第  63 块 :67108864 * 63 ~ (67108864 * (63+ 1) - 1)  <==> 4227858432 ~ 4294967295

举例如何求出每个数据对应的块:比如当前数是 3422552090

对应块的索引:3422552090 / 67108864 = 51

然后让该块的总数量+1,即:countArr[51]++
最后遍历countArr,判断哪个块的总数量 < 67108864,就找到了不存在的整数在这个块中。

2. 明确了所在块,找出不存在的值即可

假设找到第37块上的总数 < 67108864,那就对40亿数据进行第二次遍历,过滤掉不在37块上的数。
对于在37块上的数据,继续使用上一节的位存储方案处理。将每个数据投射在位图中对应位置,设置值为1。
最后遍历位图,当找到位置值为0,根据此时的位偏移位图索引就能算出这个不存在的值了
举例:不存在的值=位偏移 + 位图索引*32 + 37*67108864

Go代码

源码地址: GitHub-golang版本(含单元测试代码)

func FindTargetIndex(arr []int) (targerIndex int) {
	N := 67108864
	countArr := make([]int, 64) //大约占用 0.25K 内存空间
	// 遍历40亿数据,明确每个数据对应的块,统计落在每个块上的总数量
	for _, num := range arr {
		countArr[num/N]++
	}
	for i, count := range countArr {
		if count < N {
			return i
		}
	}
	return -1
}
func FindNoExistNumBy10M(arr []int) (res int) {
	N := 67108864
	targetIndex := FindTargetIndex(arr)
	bitmap := make([]int, N/32+1) //大约占用 8M 内存空间
	for _, num := range arr {
		// 如果数据是属于目标块的
		if num/N == targetIndex {
			val := num - targetIndex*N
			val0 := val - 1
			index := val0 / 32
			offset := val0 % 32
			mark := 1 << offset
			bitmap[index] |= mark
		}
	}
	for index, intval := range bitmap {
		for i := 0; i < 32; i++ {
			mark := 1 << i
			// 找到不存在的值
			if intval&mark == 0 {
				res = i + index*32 + targetIndex*N + 1
				return res
			}
		}
	}
	return -1
}

题目:用 2GB 内存在 20亿 个整数中找到出现次数最多的数

要求:有一个包含 20 亿个全是 32 位整数的大文件,在其中找到出现次数最多的数。
要求,内存限制为 2GB。

哈希表统计

在找出现次数最多的数时,一般做法是使用哈希表,其中键是整数,值是对应整数的出现次数。

确定存储结构的数据类型(考虑值溢出问题)

int的最大值是:2^31-1 = 2147483647
2147483647 > 20亿,所以可以使用int类型表示key和val(极端情况:如果有整数出现20亿次,或者有20亿个不同的整数,都不会有溢出问题)

所以,哈希map的结构为:map[int]int
由于每个整数是32位,所以一个键需要4字节,一个值也需要4字节,所以每个键值对需要8字节

哈希函数分割文件(考虑 2GB 内存能存储多少个值)

2GB = 210241024*1024 = 2147483648(byte)
2147483648 / 8 = 268435456
2亿 < 268435456 < 3亿

所以,将20亿分为每块2亿进行处理,共10块,这样处理每块的内存 < 2GB 内存,满足题意。

但一般来说,我们划分都是使用2的整数倍,因此这里划分成16块是更合理的。

确保将相同的数分配到同一个小文件中

哈希函数的作用是将输入的数据映射到一个较小的范围,例如0到15。这样,相同的整数会被映射到同一个范围,从而将整个大文件分割成了16个小文件。

小文件中的统计

对每个小文件,使用哈希表来统计其中每种数出现的次数。这样,每个小文件都包含了它自己的键值对,键是整数,值是对应整数的出现次数。

寻找每个小文件的第一名

对每个小文件,找出出现次数最多的数(第一名),并记录其出现次数。这样,就得到了16个小文件中各自的第一名和对应的次数。

从16个小文件中选出全局第一名

最后,从这16个小文件的第一名中选出谁出现的次数最多,就是全局的出现次数最多的数。

题目:从 100亿 个URL中查找的问题

要求:有一个包含 100 亿个 URL 的大文件,假设每个 URL 占用 64B,请找出其中所有重复的 URL。

解答:原问题的解法使用解决大数据问题的一种常规方法:把大文件通过哈希函数分配到机器, 或者通过哈希函数把大文件拆成小文件,一直进行这种划分,直到划分的结果满足资源限制的要求。首先,你要向面试官询问在资源上的限制有哪些,包括内存、计算时间等要求。在明确了限制要求之后,可以将每条 URL 通过哈希函数分配到若干台机器或者拆分成若干个小文件, 这里的“若干”由具体的资源限制来计算出精确的数量。

例如,将 100 亿字节的大文件通过哈希函数分配到 100 台机器上,然后每一台机器分别统计分给自己的 URL 中是否有重复的 URL,同时哈希函数的性质决定了同一条 URL 不可能分给不同的机器;或者在单机上将大文件通过哈希函数拆成 1000 个小文件,对每一个小文件再利用哈希表遍历,找出重复的 URL;还可以在分给机器或拆完文件之后进行排序,排序过后再看是否有重复的 URL 出现。总之,牢记一点,很多大数据问题都离不开分流,要么是用哈希函数把大文件的内容分配给不同的机器,要么是用哈希函数把大文件拆成小文件,然后处理每一个小数量的集合。

补充问题:某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门 Top 100 词汇的可行办法。

补充问题最开始还是用哈希分流的思路来处理,把包含百亿数据量的词汇文件分流到不同的机器上,具体多少台机器由面试官规定或者由更多的限制来决定。对每一台机器来说,如果分到的数据量依然很大,比如,内存不够或存在其他问题,可以再用哈希函数把每台机器的分流文件拆成更小的文件处理。处理每一个小文件的时候,通过哈希表统计每种词及其词频,哈希表记录建立完成后,再遍历哈希表,遍历哈希表的过程中使用大小为 100 的小根堆来选出每一个小文件的 Top 100(整体未排序的 Top 100)。每一个小文件都有自己词频的小根堆(整体未排序的 Top 100),将小根堆里的词按照词频排序,就得到了每个小文件的排序后 Top 100。然后把各个小文件排序后的 Top 100 进行外排序或者继续利用小根堆,就可以选出每台机器上的 Top100。不同机器之间的 Top 100 再进行外排序或者继续利用小根堆,最终求出整个百亿数据量中的 Top 100。对于 Top K 的问题,除用哈希函数分流和用哈希表做词频统计之外,还经常用堆结构和外排序的手段进行处理。

总结处理大规模数据的技巧方法:哈希分流、哈希表、堆排序、位图

题目:40 亿个非负整数中找到出现两次的数

要求:32 位无符号整数的范围是 0~4 294 967 295,现在有 40 亿个无符号整数,可以使用最多 1GB 的内存,找出所有出现了两次的数。

本题可以看做第一题的进阶问题,这里将出现次数限制在了两次。

  1. 首先,需要一个位图数组 bitArr,该数组的长度是 4,294,967,295 × 2,每个数需要两个位置(bit)来表示其出现的情况。由于每个 bit 占用 1/8 个字节(1 byte),所以数组长度为 4,294,967,295 × 2 / 8 bytes,大约占用 1GB 的内存空间。
  2. 遍历这 40 亿个无符号数。对于每个数 num,我们检查它在 bitArr 中的对应位置,即 bitArr[num * 2] 和 bitArr[num * 2 + 1]。
    • 如果 bitArr[num * 2] 和 bitArr[num * 2 + 1] 都是 0,说明这是第一次遇到该数,我们将其设置为 01(01表示出现了一次)。
    • 如果 bitArr[num * 2] 和 bitArr[num * 2 + 1] 是 01,说明这是第二次遇到该数,我们将其设置为 10(10表示出现了两次)。
    • 如果 bitArr[num * 2] 和 bitArr[num * 2 + 1] 已经是 10,说明这是第三次及以上遇到该数,我们将其保持为 10,不再变动。
  3. 遍历完成后,我们再次遍历 bitArr。对于每个数 i,如果发现 bitArr[i * 2] 和 bitArr[i * 2 + 1] 的值是 10,说明这个数出现了两次,我们可以将 i 加入到结果集中。

Go代码

func findDuplicates(arr []uint32) []uint32 {
    bitArrLen := uint64(4294967295) * 2
    bitArr := make([]byte, (bitArrLen/8)+1)

    duplicates := []uint32{}

    for _, num := range arr {
        index := num * 2
        if (bitArr[index/8] & (1 << (index % 8))) == 0 {
            bitArr[index/8] |= 1 << (index % 8)
        } else if (bitArr[(index+1)/8] & (1 << ((index + 1) % 8))) == 0 {
            bitArr[(index+1)/8] |= 1 << ((index + 1) % 8)
            duplicates = append(duplicates, num)
        }
    }
    
    return duplicates
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/93857.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BMP图片读写实践:rgb转bgr

本实理论上支持24位图和32位图&#xff0c;实际上只测试了24位。原理很简单&#xff0c;就是RGB中的蓝色字节和红色字节交换。 测试代码1&#xff1a; #include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <stdlib.h> #include &l…

回归预测 | MATLAB实现TSO-ELM金枪鱼群优化算法优化极限学习机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现TSO-ELM金枪鱼群优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现TSO-ELM金枪鱼群优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效…

初试Eureka注册中心

Eureka是spring cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)P(分区容错性)。一个Eureka中分为eureka server和eureka client。其中eureka server是作为服务的注册与发现中心。 搭建eureka服务 引入eureka依赖 引入SpringCloud为eureka提供的starter依…

橙河:海外賺美金的项目有哪些?

大家好&#xff0c;我是橙河老师。现在呢&#xff0c;很多人去国外打工&#xff0c;大家在短视频上也经常能看到&#xff0c;他们在国外挣了几年钱&#xff0c;回来就能买车买房。 其实呢&#xff0c;他们在国外的工作&#xff0c;工资也就是几千块一个月&#xff0c;不过他们…

C语言数值表示——进制、数值存储方式

进制 进制也就是进位制&#xff0c;是人们规定的一种进位方法对于任何一种进制—X进制&#xff0c;就表示某一位置上的数运算时是逢X进一位 十进制是逢十进一&#xff0c;十六进制是逢十六进一&#xff0c;二进制就是逢二进一&#xff0c;以此类推&#xff0c;x进制就是逢x进位…

通俗理解DDPM到Stable Diffusion原理

代码1&#xff1a;stabel diffusion 代码库代码2&#xff1a;diffusers 代码库论文&#xff1a;High-Resolution Image Synthesis with Latent Diffusion Models模型权重&#xff1a;runwayml/stable-diffusion-v1-5 文章目录 1. DDPM的通俗理解1.1 DDPM的目的1.2 扩散过程1.3 …

编写Dockerfile制作Web应用系统nginx镜像,生成镜像nginx:v1.1,并推送其到私有仓库。

环境&#xff1a; CentOS 7 Linux 3.10.0-1160.el7.x86_64 具体要求如下&#xff1a; &#xff08;1&#xff09;基于centos基础镜像&#xff1b; &#xff08;2&#xff09;指定作者信息&#xff1b; &#xff08;3&#xff09;安装nginx服务&#xff0c;将提供的dest目录…

Linux学习之DNS服务的原理

DNS服务一些理论 域名系统&#xff08;Domain Name System&#xff0c;DNS&#xff09;是互联网的核心应用服务&#xff0c;可以通过IP地址查询到域名&#xff0c;也可以通过域名查询到IP地址。 FQDN&#xff08;Full Qualified Domain Name&#xff09;是完全限定域名&#xf…

Apache Paimon 实时数据湖 Streaming Lakehouse 的存储底座

摘要&#xff1a;本文整理自阿里云开源大数据表存储团队负责人&#xff0c;阿里巴巴高级技术专家李劲松&#xff08;之信&#xff09;&#xff0c;在 Streaming Lakehouse Meetup 的分享。内容主要分为四个部分&#xff1a; 流计算邂逅数据湖 Paimon CDC 实时入湖 Paimon 不止…

java八股文面试[多线程]——死锁、活锁、饥饿

DCL双重锁&#xff1a;TODO 如何预防死锁&#xff1a; 如何查看线程死锁&#xff1a; 知识来源&#xff1a; 【2023年面试】描述一下线程安全活跃态问题&#xff0c;以及竞态条件_哔哩哔哩_bilibili 【2023年面试】如何预防死锁_哔哩哔哩_bilibili 【并发与线程】阿里一面&…

element Collapse 折叠面板 绑定事件

1. 点击面板触发事件 change <el-collapse accordion v-model"activeNames" change"handleChange"><el-collapse-item title"一致性 Consistency"><div>与现实生活一致&#xff1a;与现实生活的流程、逻辑保持一致&#xff0c…

迅为RK3588开发板Android12 设置系统默认不锁屏

修改 frameworks/base/packages/SettingsProvider/res/values/defaults.xml 文件&#xff0c;修改为如下 所示&#xff1a; - <bool name"def_lockscreen_disabled">false</bool> <bool name"def_lockscreen_disabled">true</bool&…

怎么建设ITIIL运维管理体系?

市场上大多数ITIL解决方案都过于复杂&#xff0c;让我们举一个客户希望实施ITIL方案的例子。首先&#xff0c;客户要通过ITIL咨询来定义ITIL流程&#xff0c;并使其与业务目标保持一致。接下来就是购买ITIL软件&#xff1b;大多数ITIL解决方案将事件、问题和变更管理作为不同的…

一生一芯9——ubuntu22.04安装valgrind

这里安装的valgrind版本是3.19.0 下载安装包 在选定的目录下打开终端&#xff0c;输入以下指令 wget https://sourceware.org/pub/valgrind/valgrind-3.19.0.tar.bz2直至下载完成 解压安装包 输入下面指令解压安装包 tar -xvf valgrind-3.19.0.tar.bz2.tar.bz2注&#xf…

Jmeter(二十八):beanshell的使用

Beanshell 是一种轻量级的 Java 脚本,纯 Java 编写的,能够动态的执行标准 java 语法及一些扩展脚本语法,类似于 javaScript,在工作中可能用的多的就是: Beanshell 取样器:跟Http取样器并列Beanshell前置处理器:一般放在Http请求下,在请求前处理一些数据Beanshell后置处…

1.6 服务器处理客户端请求

客户端进程向服务器进程发送一段文本&#xff08;MySQL语句&#xff09;&#xff0c;服务器进程处理后再向客户端进程发送一段文本&#xff08;处理结果&#xff09;。 从图中我们可以看出&#xff0c;服务器程序处理来自客户端的查询请求大致需要经过三个部分&#xff0c;分别…

关于disriminative 和 generative这两种模型

但是&#xff0c;其实&#xff0c;根据李宏毅老师讲到的&#xff0c;generative model是做了一些假设的&#xff0c;比如&#xff0c;如果使用Naive Bayes的话&#xff0c;不同特征x1,x2...之间相互独立的话&#xff0c;其实是很容易出现较大的偏差的&#xff0c;因为不同特征变…

在kaggle中用GPU使用CGAN生成指定mnist手写数字

文章目录 1项目介绍2参考文章3代码的实现过程及对代码的详细解析独热编码定义生成器定义判别器打印我们的引导信息模型训练迭代过程中生成的图片损失函数的变化 4总结5 模型相关的文件 1项目介绍 在GAN的基础上进行有条件的引导生成图片cgan 2参考文章 GAN实战之Pytorch 使用…

UbuntuDDE 23.04发布,体验DeepinV23的一个新选择

UbuntuDDE 23.04发布&#xff0c;体验DeepinV23的一个新选择 昨晚网上搜索了一圈&#xff0c;无意看到邮箱一条新闻&#xff0c;UbuntuDDE 23.04发布了 因为前几天刚用虚拟机安装过&#xff0c;所以麻溜的从网站下载了ISO文件&#xff0c;安装上看看。本来没多想&#xff0c;…

什么是字体图标(Icon Font)?如何在网页中使用字体图标?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 字体图标&#xff08;Icon Font&#xff09;⭐ 如何在网页中使用字体图标⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&a…
最新文章