阿里面试,让我十五分钟内手写 LRU。。。

2023-11-03 15:40

本文主要是介绍阿里面试,让我十五分钟内手写 LRU。。。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

点击上方“五分钟学算法”,选择“星标”公众号

重磅干货,第一时间送达

你面试的时候遇见过LRU吗?

LRU 算法,全称是Least Recently Used。

翻译过来就是最近最少使用算法。

这个算法的思想就是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。所以,当指定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。

听描述你也知道了,它是一种淘汰算法。

这个算法也是面试的一个高频考点。

有的面试官甚至要求手撸一个 LRU 算法出来。

其实我觉得吧,遇到这种情况也不要慌,你就按照自己的思路写一个出来就行。

赌一把,面试官也许自己短时间内都手撸不出来一个无 bug 的 LRU。他也只是检查几个关键点、看看你的代码风格、观察一下你的解题思路而已。

但其实大多数情况下面试场景都是这样的:

面试官:你知道 LRU 算法吗?

我:知道,翻译过来就是最近最少使用算法。其思想是(前面说过,就不复述了)......

面试官:那你能给我谈谈你有哪些方法来实现 LRU 算法呢?

这个时候问的是什么?

问的是:我们都知道这个算法的思路了,请你按照这个思路给出一个可以落地的解决方案。

不用徒手撸一个。

方案一:数组

如果之前完全没有接触过 LRU 算法,仅仅知道其思路。

第一次听就要求你给一个实现方案,那么数组的方案应该是最容易想到的。

假设我们有一个定长数组。数组中的元素都有一个标记。这个标记可以是时间戳,也可以是一个自增的数字。

我这里用自增的数字。

每放入一个元素,就把数组中已经存在的数据的标记更新一下,进行自增。当数组满了后,就将数字最大的元素删除掉。

每访问一个元素,就将被访问的元素的数字置为 0 。

这不就是 LRU 算法的一个实现方案吗?

按照这个思路,撸一份七七八八的代码出来,问题应该不大吧?

但是这一种方案的弊端也是很明显:需要不停地维护数组中元素的标记。

那么你觉得它的时间复杂度是多少?

是的,每次操作都伴随着一次遍历数组修改标记的操作,所以时间复杂度是 O(n)。

但是这个方案,面试官肯定是不会满意的。因为,这不是他心中的标准答案。

也许他都没想过:你还能给出这种方案呢?

但是它不会说出来,只会轻轻的说一句:还有其他的方案吗?

方案二:链表

于是你扣着脑壳想了想。最近最少使用,感觉是需要一个有序的结构。

我每插入一个元素的时候,就追加在数组的末尾。

等等。

我每访问一个元素,也要把被访问的元素移动到数组的末尾。

这样最近被用的一定是在最后面的,头部的就是最近最少使用的。

当指定长度被用完了之后,就把头部元素移除掉就行了。

这是个什么结构?

这不就是个链表吗?

维护一个有序单链表,越靠近链表头部的结点是越早之前访问的。

当有一个新的数据被访问时,我们从链表头部开始顺序遍历链表。

如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据的对应结点,并将其从原来的位置删除,并插入到链表尾部。

如果此数据没在缓存链表中,怎么办?

分两种情况:

  • 如果此时缓存未满,可直接在链表尾部插入新节点存储此数据;

  • 如果此时缓存已满,则删除链表头部节点,再在链表尾部插入新节点。

你看,这不又是 LRU 算法的一个实现方案吗?

按照这个思路,撸一份八九不离十的代码出来,问题应该不大吧?

这个方案比数组的方案好在哪里呢?

我觉得就是莫名其妙的高级感,就是看起来就比数组高级了一点。

从时间复杂度的角度看,因为链表插入、查询的时候都要遍历链表,查看数据是否存在,所以它还是O(n)。

总之,这也不是面试官想要的答案。

当你回答出这个方案之后,面试官也许会说:你能不能给我一个查询和插入的时间复杂度都是O(1)的解决方案?

到这里,如果第一次遇到这题,就得看天分了。

有一说一,如果我之前完全没有接触过 LRU 算法,我可以非常自信的说:

方案三:双向链表+哈希表

如果我们想要查询和插入的时间复杂度都是 O(1),那么我们需要一个满足下面三个条件的数据结构:

  • 1.首先这个数据结构必须是有时序的,以区分最近使用的和很久没有使用的数据,当容量满了之后,要删除最久未使用的那个元素。

  • 2.要在这个数据结构中快速找到某个 key 是否存在,并返回其对应的 value。

  • 3.每次访问这个数据结构中的某个 key,需要将这个元素变为最近使用的。也就是说,这个数据结构要支持在任意位置快速插入和删除元素。

那么,你说什么样的数据结构同时符合上面的条件呢?

查找快,我们能想到哈希表。但是哈希表的数据是乱序的。

有序,我们能想到链表,插入、删除都很快,但是查询慢。

所以,我们得让哈希表和链表结合一下,成长一下,形成一个新的数据结构,那就是:哈希链表,LinkedHashMap。

这个结构大概长这样:

借助这个结构,我们再来分析一下上面的三个条件:

  • 1.如果每次默认从链表尾部添加元素,那么显然越靠近尾部的元素就越是最近使用的。越靠近头部的元素就是越久未使用的。

  • 2.对于某一个 key ,可以通过哈希表快速定位到链表中的节点,从而取得对应的 value。

  • 3.链表显示是支持在任意位置快速插入和删除的,修改指针就行。但是单链表无非按照索引快速访问某一个位置的元素,都是需要遍历链表的,所以这里借助哈希表,可以通过 key,快速的映射到任意一个链表节点,然后进行插入和删除。

这才是面试官想要关于 LRU 的正确答案。

但是你以为回答到这里就结束了吗?

面试官为了确认你的掌握程度,还会追问一下。

那么请问:为什么这里要用双链表呢,单链表为什么不行?

你心里一慌:我靠,这题我也背过。一时想不起来了。

所以,别只顾着背答案,得理解。

你想啊,我们是不是涉及到删除元素的操作?

那么链表删除元素除了自己本身的指针信息,还需要什么东西?

是不是还需要前驱节点的指针?

那么我们这里要求时间复杂度是O(1),所以怎么才能直接获取到前驱节点的指针?

这玩意是不是就得上双链表?

咦,你看在一波灵魂追问中,就得到了答案。

面试官的第二个问题又随之而来了:哈希表里面已经保存了 key ,那么链表中为什么还要存储 key 和 value 呢,只存入 value 不就行了?

不会,也不要慌,你先分析一波。

刚刚我们说删除链表中的节点,需要借助双链表来实现 O(1)。

删除了链表中的节点,然后呢?

是不是还忘记了什么东西?

是不是还有一个哈希表忘记操作了?

哈希表是不是也得进行对应的删除操作?

删除哈希表需要什么东西?

是不是需要 key,才能删除对应的 value?

这个 key 从哪里来?

是不是只能从链表中的结点里面来?

如果链表中的结点,只有 value 没有 key,那么我们就无法删除哈希表的 key。那不就完犊子了吗?

又是一波灵魂追问。

所以,你现在知道答案了吗?

另外在多说一句,有的小伙伴可能会直接回答借助 LinkedHashMap 来实现。

我觉得吧,你要是实在不知道,也可以这样说。

但是,这个回答可能是面试官最不想听到的回答了。

他会觉得你投机取巧。

但是呢,实际开发中,真正要用的时候,我们还是用的 LinkedHashMap。

而且更多的实际情况是,你开发,写业务代码的时候,根本就不会用到 LRU 算法。

你说这个事情,难受不难受。

好了,你以为到这里面试就结束了?

天真。

LRU 在 MySQL 中的应用

面试官:小伙子刚刚 LRU 回答的不错哈。要不你给我讲讲,LRU 在 MySQL 中的应用?

LRU 在 MySQL 的应用就是 Buffer Pool,也就是缓冲池。

它的目的是为了减少磁盘 IO。

缓冲池具体是干啥的,我这里就不展开说了。

你就知道它是一块连续的内存,默认大小 128M,可以进行修改。

这一块连续的内存,被划分为若干默认大小为 16KB 的页。

既然它是一个 pool,那么必然有满了的时候,怎么办?

就得移除某些页了,对吧?

那么问题就来了:移除哪些页呢?

刚刚说了,它是为了减少磁盘 IO。所以应该淘汰掉很久没有被访问过的页。

很久没有使用,这不就是 LRU 的主场吗?

但是在 MySQL 里面并不是简单的使用了 LRU 算法。

因为 MySQL 里面有一个预读功能。预读的出发点是好的,但是有可能预读到并不需要被使用的页。

这些页也被放到了链表的头部,容量不够,导致尾部元素被淘汰。

哦豁,降低命中率了,凉凉。

还有一个场景是全表扫描的 sql,有可能直接把整个缓冲池里面的缓冲页都换了一遍,影响其他查询语句在缓冲池的命中率。

那么怎么处理这种场景呢?

把 LRU 链表分为两截,一截里面放的是热数据,一截里面放的是冷数据。

打住,不能再说了。

再说就是另外一篇文章了,点到为止。

你就了解在 MySQL 里面,LRU 是有一个变种的。

如果你不清楚,建议去学习一下哦。

只要你学的够快,你就会被卷的越慢。

LRU 在 Redis 中的应用

既然是内存淘汰算法,那么我们常用的 Redis 里面必然也有对应的实现。

Redis 的内存淘汰策略有如下几种:

  • noenviction:默认策略。不继续执行写请求(DEL 请求可以处理),读请求可以继续进行。

  • volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。没有设置过期时间的 key 不会被淘汰。

  • volatile-random:从已设置过期时间的数据集中随机选择数据淘汰。

  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。

  • allkeys-lru:和 volatile-lru 不同的是,这个策略要淘汰的 key 对象是全体的 key 集合。

  • allkeys-random:从所有数据集中随机选择数据淘汰。

关于 Redis 中的 LRU 算法,官网上是这样说的:

https://github.com/redis/redis-doc/blob/master/topics/lru-cache.md

在 Redis 中的 LRU 算法不是严格的 LRU 算法。

Redis 会尝试执行一个近似的LRU算法,通过采样一小部分键,然后在采样键中回收最适合的那个,也就是最久没有被访问的那个(with the oldest access time)。

然而,从 Redis 3.0 开始,改善了算法的性能,使得更接近于真实的 LRU 算法。做法就是维护了一个回收候选键池。

Redis 的 LRU 算法有一个非常重要的点就是你可以通过修改下面这个参数的配置,自己调整算法的精度。

maxmemory-samples 5

而上面截图中,我认为最重要的一句话也已经标志出来了:

The reason why Redis does not use a true LRU implementation is because it costs more memory.

Redis 没有使用真实的 LRU 算法的原因是因为这会消耗更多的内存。

然后官网上给了一个随机 LRU 算法和严格 LRU 算法的对比图:

对于这个图官网是这样说的:

你可以从图中看到三种不同的小圆点形成的三个不同的带:

  • 浅灰色带是被回收(被 LRU 算法淘汰)的对象

  • 灰色带是没有被回收的对象

  • 绿色带是新添加的对象

由于 Redis 3.0 对 LRU 算法进行了改进,增加了淘汰池。

所以你可以看到,同样使用 5 个采样点,Redis 3.0 表现得比 Redis 2.8 要好。

同时可以看出,在 Redis 3.0 中使用 10 为采样大小,近似值已经非常接近理论性能。

写到这里我突然想起了另外一个面试题。

数据库中有 3000w 的数据,而 Redis 中只有 100w 数据,如何保证 Redis 中存放的都是热点数据?

这个题你说它的考点是什么?

考的就是淘汰策略呀,同志们,只是方式比较隐晦而已。

我们先指定淘汰策略为 allkeys-lru 或者 volatile-lru,然后再计算一下 100w 数据大概占用多少内存,根据算出来的内存,限定 Redis 占用的内存。

接下来的,就交给 Redis 的淘汰策略了。

搞定。


推荐阅读

•   吴师兄实名吐槽 LeetCode 上的一道题目。。。•   面试字节跳动时,我竟然遇到了原题……•   计算机专业的学生怎样练习编程才能把编程学精通?•   为什么 MySQL 使用 B+ 树•   一道简简单单的字节跳动算法面试题


欢迎关注我的公众号“五分钟学算法”,如果喜欢,麻烦点一下“在看”,点击左下方阅读原文,获取谷歌师兄的算法刷题笔记。

这篇关于阿里面试,让我十五分钟内手写 LRU。。。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/339183

相关文章

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

java面试常见问题之Hibernate总结

1  Hibernate的检索方式 Ø  导航对象图检索(根据已经加载的对象,导航到其他对象。) Ø  OID检索(按照对象的OID来检索对象。) Ø  HQL检索(使用面向对象的HQL查询语言。) Ø  QBC检索(使用QBC(Qurey By Criteria)API来检索对象。 QBC/QBE离线/在线) Ø  本地SQL检索(使用本地数据库的SQL查询语句。) 包括Hibern

贝壳面试:什么是回表?什么是索引下推?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题: 1.谈谈你对MySQL 索引下推 的认识? 2.在MySQL中,索引下推 是如何实现的?请简述其工作原理。 3、说说什么是 回表,什么是 索引下推 ? 最近有小伙伴在面试 贝壳、soul,又遇到了相关的

LRU 实现原理

前言 我们常用缓存提升数据查询速度,由于缓存容量有限,当缓存容量到达上限,就需要删除部分数据挪出空间,这样新数据才可以添加进来。缓存数据不能随机删除,一般情况下我们需要根据某种算法删除缓存数据。常用淘汰算法有 LRU,LFU,FIFO,这篇文章我们聊聊 LRU 算法。 LRU 简介 LRU 是 Least Recently Used 的缩写,这种算法认为最近使用的数据是热门数据,下一次很大概

毕业前第二次面试的感慨

距面试已经过去了有几天了,我现在想起来都有说多的恨感慨。 我一直都是想找刚刚起步的企业,因为这能让我学到更多的东西,然而正好有一家企业是刚起步的,而且他还有自己的产品专利,可以说这是一家,即是创业又是刚起步的公司,这家公司回复了我投给他的简历,这家企业想进一步了解我的情况,因为简历上我符合这家企业的基本要求,所以要进一步了解。 虽然面试的过程中,他给我的面试题,我做得并不是很理想,

阿里云服务器ces

允许公网通过 HTTP、HTTPS 等服务访问实例 https://help.aliyun.com/document_detail/25475.html?spm=5176.2020520101.0.0.3ca96b0b3KGTPq#allowHttp

LLM系列 | 38:解读阿里开源语音多模态模型Qwen2-Audio

引言 模型概述 模型架构 训练方法 性能评估 实战演示 总结 引言 金山挂月窥禅径,沙鸟听经恋法门。 小伙伴们好,我是微信公众号《小窗幽记机器学习》的小编:卖铁观音的小男孩,今天这篇小作文主要是介绍阿里巴巴的语音多模态大模型Qwen2-Audio。近日,阿里巴巴Qwen团队发布了最新的大规模音频-语言模型Qwen2-Audio及其技术报告。该模型在音频理解和多模态交互

腾讯社招面试经历

前提:本人2011年毕业于一个普通本科,工作不到2年。   15号晚上7点多,正在炒菜做饭,腾讯忽然打电话来问我对他们的Linux C++的职位是否感兴趣,我表达了我感兴趣之后,就开始了一段简短的电话面试,电话面试主要内容:C++和TCP socket通信的一些基础知识。之后就问我一道算法题:10亿个整数,随机生成,可重复,求最大的前1万个。当时我一下子就蒙了,没反应过来,何况我还正在烧