【面试】运算器-③url去重

前几天在leetcode上看到彭寅大佬发布的面试题:

10 亿个 url,每个 url 大小小于 56B,要求去重,内存 4G。

1.分析

        看到这个题目是懵的,10亿个url,每个56B,那就是560亿Byte,那就是52G的量,要去重,不上算法是不可能的了。就是每一次我只能十几分之一的内存使用,想到了哈希表,有没有可能这个去重完保存的还是大于4G的量,那可咋整。还有别的好的办法吗?折半法?先用哈希来处理一半,再用哈希处理一半,再把两个一半合起来这样的方式可以吗?题目的意思我理解了,步骤列一下:

        ① 假如是面试,没有遇到怎么回?

        ② 查一下GPT,看GPT怎么回。

        ③ 假如又来了面试,怎么回?

        ④ 查一下网上别人的答案。

        ⑥ 总结

2.小白回复

        收到,可以给我两分钟思考一下吗,我整理一下回复给您。

        ======两分钟后======

        您好,是这样的,有几个前提想和您确认一下:

                ① 会不会十亿个URL都不重复?

                ② 对于解决方案有什么限制吗?对于编程语言和语法这一块有要求吗?

        好的,我初步的思路是使用哈希表来进行去重,哈希表可以将变长的字符通过算法转换为定长的key值,可以帮助我们避免在内存中保存太多待判断的重复值而出现的内存溢出问题,综上我认为使用哈希表可以帮助我们在4G的内存下完成10亿个URL的去重;

3.查GPT

处理10亿个URL的去重任务时,考虑到每个URL小于56字节,而整体内存限制为4GB,我们需要使用一种内存高效的方法来实现。这里有几种策略可以采用:

1. 使用布隆过滤器(Bloom Filter)

布隆过滤器是一种空间效率极高的数据结构,用于快速判断一个元素是否在一个集合中。它允许一定的误报率(false positives),但不允许漏报(false negatives)。对于每个URL,布隆过滤器可以判断出URL是否可能已经存在。如果不存在,则添加到布隆过滤器中,并保存该URL。这个方法非常节省空间,但需要在最后再次检查可能的误报。

2. 外部排序和去重

由于内存限制,我们不能直接在内存中处理所有URL。一种方法是使用外部排序算法,如归并排序:

  1. 分割:将URL分成多个批次,每批可以放入内存中。

  2. 排序:对每个批次进行排序,并将结果写回到磁盘。

  3. 归并:逐个归并排序好的批次,并在归并过程中去重。

这个方法虽然在磁盘I/O方面效率不是很高,但是可以实现去重且不受内存大小的限制。

3. 哈希表分割

  1. 预处理:使用一个简单的哈希函数将URL按哈希值分布到不同的桶(bucket)中。这样做的目的是将数据分散,确保每个桶中的URL数量可以被内存容纳。

  2. 单独处理:加载每个桶中的URL到内存,使用哈希集合去重,然后将去重后的结果保存回磁盘。

4. Trie树(前缀树)

Trie树是一种空间效率高的数据结构,特别适合于处理字符串集合的查找问题。我们可以使用Trie树来存储所有的URL,通过Trie树的结构来实现去重。这种方法在处理大量字符串数据时可以节省大量空间,但是对于10亿个URL可能仍然超出内存限制。

总结

对于实际操作,可能需要根据URL的实际特征和分布,结合以上方法进行灵活应用。例如,可以先用布隆过滤器快速过滤掉绝大多数重复的URL,然后对剩下的进行外部排序或使用Trie树去重。在处理如此大量的数据时,考虑到效率和资源限制,通常需要在时间、空间以及实现复杂度之间做出权衡。

        Woo~,竟然有四个方法,布隆过滤器这个完全没有听说过,其他的外部排序、哈希表和Trie树都听说过,没有具体的了解过,接下来就趁着这个机会对这几个来进行进一步的学习。

3.1 布隆过滤器

        问题1:什么是布隆过滤器?

        回答1:快速判断元素是否在集合里面,可能会错误的报这个元素在集合里,但是不会错误的报这个而元素不在集合里。

        问题2:有什么特点?

        回答2:会误报!不能从过滤器中删除元素。

        问题3:JAVA怎么用?

        回答3:google的guava提供了BloomFilter;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

public class BloomFilterExample {
    public static void main(String[] args) {
        // 创建一个布隆过滤器,预计插入1000个元素,误报率为0.01
        BloomFilter<Integer> filter = BloomFilter.create(
                Funnels.integerFunnel(), 
                1000, 
                0.01);

        // 添加元素
        filter.put(1);
        filter.put(2);
        filter.put(3);

        // 检查元素是否可能存在
        System.out.println(filter.mightContain(1)); // true
        System.out.println(filter.mightContain(2)); // true
        System.out.println(filter.mightContain(3)); // true
        System.out.println(filter.mightContain(100)); // false,但实际上可能误报为true

    }
}

3.2 外部排序

        这个其实也想到了,但是没有在回答里面表述出来,通过排序的方式可以采取不同的指针来判断是否重复,只是我想着字符串排序有点麻烦,可能要消耗更多的内存,所以就漏了,但是现在想起来不失为一个方案,面试的时候说两个总比一个要好。

3.3 哈希表

        这个有一个点是要分割哈希表,其实一直很好奇哈希表是怎么做的,今天正好研究一下,死去的记忆慢慢苏醒了,哈哈,我的理解的话,哈希表有几个关键点:

        ① 一定要有一个哈希函数,这个函数就是哈希表的关键;

        ② 一定要有一个碰撞处理方案;

        ③ 一定要明确扩容方案;

        分别聊天一下这三个关键点,首先是第一个哈希函数,这里聊一下字符串哈希表、数字哈希表、对象哈希表三个哈希函数:

        ① 字符串哈希表:可以对每一个字符的ASCII码拿出来乘以一个质数的幂次方,重点是算出一个数字出来帮助建立哈希表;

        ② 数字哈希表:可以采取mod哈希表长度的方式进行;

        ③ 对象哈希表:对象有一个hashCode()方法,可以单独写算法帮助建立哈希表。

        然后是碰撞处理方案,碰撞处理方案也有几个常见的,链表法、开放寻址法:

        ① 链表法:每个表项存储为一个链表,每个链表可以无限拓长;

        ② 开放寻址法:当表象被占用的时候,用某种方法来确定下一个位置,例如跳几个呀,或者是怎么样;

        最后是扩容方案,我还以为链表法就不需要扩容了,结果查了才知道我们扩的是表项的大小,也就是哈希函数确定出来的东西叫表项,这个是有大小的,这里需要注意的就是如果要扩容的话,需要把原来的所有表项和值遍历出来存储到新的哈希表中。

3.4 Trie树(前缀树)

        看起来比较复杂,反正后面还会有专项的题目的,这里先简单理解一下,就是把所有类型的字符串建立树出来,一共只有26个字母,所以其实树没有那么难建,我试一下,如果以后错了,我再回来改,就随便拿几个字符串吧:Red, Blue, Green,  Purple, Pink, Black, Gray,建树过程如下所示:

这样判断重复不重复就只要搜索就好,这样真的会快一点吗?我也不知道,以后再说~!

4.小灰回复

        面试官您好,我这边需要一分钟左右的时间思考一下,您看可以吗?

        ========一分钟后=======

        您好,根据题目所给的条件,我大概有三个思考方向:

        ① 使用分治法,将10亿的数据拆分为100份,对100份分别进行排序和去重,最后在把一百份的数据二次排序去重来降低内存消耗;

        ② 使用哈希表,有工具类直接使用工具类,没有工具类的情况下根据字符串特点设计好哈希函数、碰撞方案和扩容方案来保证在4G内存下完成去重10亿条数据的操作;

        ③ 使用前缀树,通过判断链接是否存在的方式来建立前缀树,最后通过遍历前缀树来得到所有不重复的链接;

        我的回答完毕。

5.看看大佬怎么说

       谢谢大佬!面试- 阿里-. 大数据题目- 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? - aspirant - 博客园 (cnblogs.com)

        哇塞,完全没有想到用文件这件事,确实hash+分治的方式比较合适,先用哈希进行预先筛选,然后不管是分文件还是分机器我都没有考虑到,还是局限在单机单任务上了,后面看到内存相关的题目可以多考虑一下分文件操作,这样就可以释放内存了,因为别人又没有限制硬盘的使用,厉害啊!

6.总结

        整体看下来几点收获吧,首先GPT还是很厉害的,可以拓宽知识面,真第一次听到布隆过滤器,不知道以后会不会忘记,其实应该要查一下看是什么东西;其次就是思路不要被单机局限,现在是云时代,包括AI的显卡都应该是越少越好,分治这个思想,真的是很厉害的思想;然后是回顾了哈希的三个关键点,虽然不一定对,但是后面回答问题可以更加有逻辑,创建、冲突和优化,是每个团队、事情都要面临的事情(PMP沟通管理乱入),最后就是发现真的,程序员可太好玩了。

        

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值