金三银四,又到了换工作的黄金期。各位小伙伴们都准备好了吗?

这句话大家是不是最近已经要看吐了呢?

每当这个时候,就证明招聘旺季又来啦~

春招、校招、社招……

那你真的准备好了吗?


现在程序员的面试,尤其是大厂程序员面试其实越来越看重算法基本功。所以想要去大厂,拿到一个心仪的offer,扎实的算法基本功必不可少。

今天牛牛就来跟大家来分享一个非常高频的算法面试题——LRU缓存淘汰算法。相信不少小伙伴在面试过程中都遇到过,这也是去年牛牛在腾讯三面时遇到的问题。

三面面试官上来首先天马行空地考察了一些基础的知识点,比如编程语言、常用中间件原理等等,虽然问题看起来简单,但咱也不能掉以轻心——这不是暗搓搓地对我们知识面广度的考察吗!接下来…相信认真读了牛牛上一篇文章的小伙伴也知道,就是redis缓存问题了!然而,你以为三面就这样结束了吗?不不不,还有LRU算法在等着你!

先别着急往下看,花个十秒钟想想,你觉得面试官会问你关于LRU的什么问题呢?

再花十秒钟问自己,这些问题你心中有答案吗?

下面牛牛就带大家走进面试现场,来一窥鹅厂面试官究竟考察了LRU的哪些知识点,看看和你想的是不是一样的。

LRU的应用场景

前面你对redis的一些存储原理答得还不错,那你知道redis的过期键清除策略和缓存淘汰策略吗?

过期键清除策略有三种:定时删除、定期删除以及惰性删除。redis过期键采用的是定期删除+惰性删除二者结合的方式进行删除的。redis的缓存淘汰策略则是采用LRU缓存淘汰算法。

从问题可以看出,在面试中,面试官通常不会一上来就要你手写一个LRU算法,往往是跟其他的知识点相结合,所以搞明白LRU的一些常用应用场景很重要。

亿点点细节,都问到算法相关的问题了,心里也要有点数了,手撕算法虽迟但到!

话说回来,其实不仅仅是在redis中有LRU的应用,在mysql中同样也有LRU的应用。

LRU在mysql中的应用就是Buffer Pool,也就是缓冲池。它的目的是为了减少磁盘IO(Input/Output),提高读取效率,所以当Buffer Pool满了的时候,也会采用LRU算法来清理缓存。但是不论是redis还是mysql都不是严格意义上的LRU算法,它们都是改进过后的近似LRU算法。你只要知道在mysql和redis中都有LRU的应用场景就好了。对其具体的算法感兴趣的小伙伴可以在留言区留言,牛牛将在后续文章中进行介绍。

LRU的含义

那你能简单讲一下这个LRU算法的思想吗?

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

LRU的全称是Least Recently Used,即最近最少使用,也就是说我们认为最近使用过的数据应该是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删掉那些很久没用过的数据。

这里说句题外话,大家在面试时对于定义不必死记硬背,追求话术的专业性,一定要自己多思考多理解,能够表达清楚即可。

接下来想必大家也知道面试官要问什么了,对,就是LRU的实现方式。

LRU的实现方案

那你能谈谈你有哪些方法可以来实现 LRU 算法吗?

啊……关键问题来了,深吸一口气,咱们不要慌!

首先我们可以利用数组实现。这里我们可以用定长数组。

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

牛牛这里使用的就是后者——自增的数字。每放入一个元素,就把数组中已经存在的数据标记更新一下,进行自增。

当数组满了之后,就将数字最大的元素删除掉。每访问一个元素,就将被访问的元素数字置为0,而剩余未被访问到的元素自增1。这样标记最大的那个元素,就是最久未被使用过的元素,当数组满了,就删除这个最久未被使用过的元素。这里我们通过图示举个例子:

假设用一个长度为4的数组来实现这个结构,按照插入到数组的顺序,数组元素分别为a、b、c、d。

我们最先插入a,标记为0;再插入b,这时b的标记为0,数组中已有元素a自增1,a的标记变为1;再插入c,c的标记为0,数组中已有元素a和b的标记自增1,a的标记为2,b的标记为1。最后插入d,前面插入的元素均自增1,这时a就是最久未被使用过的元素。

假设此时我们访问到了元素b,则b的标记变为0,剩余元素标记自增1。

随后,假设又新来了个元素e要放入数组,则我们将标记最大的a删掉,在a的位置放入e,e的标记为0,其余元素标记自增1。

这样不就可以完美的实现淘汰最久没有被访问到的数据了吗,在面试场景中,采用数组存储也是最容易想到的。当然事情远没有想象的这么简单。这一种方案的弊端也是很明显:需要不停地维护数组中元素的标记

牛牛自然知道这个方案,面试官肯定是不会满意的。因为,这不是最优方案也不是面试官想要的答案。但却是体现我们对这个题目思维过程的一个方式。

果然,面试官接着又问了。

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

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

还有可以通过链表实现。

既然不用数组,又要找到一个最久未被使用过的数据,那么就要维护一个有序的数据结构。

我们可以用链表来存储,表头存放最久未被访问过的数据,表尾存放最近刚使用过的数据,当有一个数据被访问到,就将其移动至表尾。如果链表中没有该数据,当链表长度满了,就删除表头数据,插入该新数据到表尾,如果链表未满,就直接将该数据插入到表尾。

链表对于数据的插入、删除和移动都是O(1)时间的。看样子应该很完美了,但是其实要找到我们要访问的数据,这个查找过程还是不得不遍历数组,这个时间复杂度还是O(n)的。面试官自然不会就此罢休,所以……

你这个时间查询时间复杂度还是O(n)的,你能给一个查询和插入的时间复杂度都是O(1)的解决方案吗?

早知道有此一问,嘿嘿,牛牛自然也是有备而来,是时候拿出我们压箱底的方案了!!!

双向链表 + 哈希表!

方案要满足查询和插入的时间复杂度都是O(1)的要求,分解一下,也就是我们的方案需要满足以下几点:

1.这个结构必须是有序的,要能够找到最久未使用过的数据和最近使用过的数据;

2.要能在O(1)时间内完成数据的查询和插入;

3.要能快速移动元素,每次访问一个数据之后,要能变更这个数据的位置,将其放置到最近使用过的位置上,保证整个结构有序。

对于1和2,数据的快速查询和插入可以用哈希表,而对于3可以用链表实现,将他们综合综合一下,就能满足我们的需要了。得到一个新的数据结构:

  • 对于数据的查询,我们首先通过哈希表的key查找,快速定位到链表中的节点,从而取得对应的value;

  • 对于数据的插入,每次都在链表尾部进行,直接在表尾插入数据,如果链表满了,则删除链表表头数据,再在表尾插入。所以链表表头存放着最久未被使用过的数据,表尾存放着最近使用过的数据;

  • 对于数据更新,首先通过哈希表的key快速定位到需要更新的数据在链表中是哪个节点,然后通过修改指针,快速实现节点的位置变更。将最近访问过的节点移动到链表尾部。

讲到这里,面试官脸上总算露出了些许肯定的表情。

牛牛内心缓了口气,本以为到这里这个环节可以结束了,哪里料到面试官又追问了一句:

我看你实现的时候,是用的双向链表,那这个结构为什么要用双向链表呢,单链表为什么不行

额....这个问题似曾相识,好像之前在某篇文章上看到过,但这会儿牛牛仿佛失忆了,就是想不起来。没办法,回忆不行,咱就只能自己现场分析思考了。

为什么单链表不行?

双向链表单链表唯一的区别就是前者有一个前驱指针。有哪一个操作需要前向操作?嗯...对,就是删除元素!当我们要删除元素时,若为单链表则只能从表头开始遍历,才能找到要删除节点的前一个元素,并将其连接到它的后一个元素,这就是没有前驱指针的缺点,如果是双向链表,则可以在O(1)时间内定位到删除节点的前驱节点,大大提高了效率。

到这里我们的问题也找到了答案。

但,你以为终于可以结束了吗?天真!面试官的问题又随之而来了……

哈希表里面已经保存了key,链表中只存value不就行了吗,为什么还要同时存储key和value呢?

额,准备这道题的时候,牛牛只记得链表节点都是存储了key的,至于为什么要存储key,当时并没有深入思考。

key的作用是什么?——快速定位到数据的位置。

我们在哪几个地方用到了key?——哈希表结构中。

没错,所以这个问题的答案也在哈希表中。当链表满了我们需要删除表头数据来腾出空间,但是我们删除了链表里的元素,是不是还要删除对应的哈希表中的元素?要删除这个元素是不是要知道key是什么?这个key从哪里来?没错,key是从链表的节点里来的。

一波对比分析,问题是不是又迎刃而解了。

这里,牛牛也想告诉各位小伙伴,大家在面试的时候,遇到没有准备过的问题,或者是准备过但又想不起来了,duck不必慌张,一定要去思考问题的本质是什么,解铃还须系铃人,问题的本身才是关键,多反问自己为什么!

你往往会在一波反问和分析中得出惊喜的答案,即便没有得到答案也没关系,你要给面试官展示出对于问题的思考过程,这世上的难题那么多,就算是天才也不可能都有完美回答。

而且细心的小伙伴也应该发现了,其实面试官的问题也是循序渐进的,前面提出的问题也是在引导着你一步步去解决那个最终的问题。

面试官看中的往往是一个候选人对于问题的思考过程,以及这个人对问题的解决能力,而非答案本身

回到面试现场——到这一步,面试官终于得到了满意的答案。不出意外,接下来就是紧张又刺激的手撕代码环节了。

那你能现场用这种方式实现吗?

。。。。。。。

其实关于面试过程中的手撕代码,我们没有办法逃避,也没有过多的技巧或面经,唯一的方法只有多练、多刷题,量变引起质变,当刷够了一些常用算法题之后,我们对于算法的理解自然会提升一个高度,在面试中也会不那么慌张,显得游刃有余。

好了,这次关于LRU的算法面试,牛牛就为大家分享到这里,校招将近,也预祝大家都能早日拿到一个满意的offer。

Python实现LRU

这是一个小彩蛋~

为大家奉上牛牛这次面试时用python实现的LRU:

class Node(object):        #用来存储valuedef __init__(self,key=None,value=None):self.key = keyself.value = valueself.next = Noneself.prev = Noneclass LRUCache:            #LRUCache实际上是一个map,但是其value采用双向链表的节点来存储,是为了在O(1)时间内实现元素的移动,插入和删除def __init__(self, capacity: int):self.capacity = capacity            #初始化缓存的大小self.hashmap = {}                   #用来存储键值对,O(1)时间内根据key找到key对应的节点self.head = Node()                  #创建头结点self.tail = Node()                  #创建尾结点self.head.next = self.tail          #头结点指向尾结点self.tail.prev = self.head          #尾结点指向头结点#队尾存放最近刚刚被访问过的节点,多以需要定义一个移动节点到队尾的操作def move_node_to_tail(self,key):if key not in self.hashmap:return node = self.hashmap[key]print(node.value)#把node从双向链表里孤立出来node.prev.next = node.nextnode.next.prev = node.prev#连接到队尾node.prev = self.tail.prevnode.next = self.tailself.tail.prev.next = node       self.tail.prev = nodedef get(self, key: int) -> int:if key in self.hashmap:       #可以get到,最近一次访问,所以移动对应的节点到队尾self.move_node_to_tail(key)return self.hashmap[key].valueelse:return -1def put(self, key: int, value: int) -> None:if key in self.hashmap:# 如果key本身已经在哈希表中了就不需要在链表中加入新的节点# 但是需要更新字典该值对应节点的valueself.hashmap[key].value = value# 之后将该节点移到末尾self.move_node_to_tail(key)else:if len(self.hashmap) == self.capacity:# 去掉哈希表对应项self.hashmap.pop(self.head.next.key)# 去掉最久没有被访问过的节点,即头节点之后的节点self.head.next = self.head.next.nextself.head.next.prev = self.head# 如果不在的话就插入到尾节点前new = Node(key, value)self.hashmap[key] = newnew.prev = self.tail.prevnew.next = self.tailself.tail.prev.next = newself.tail.prev = new

【腾讯三面】你能现场写一下LRU算法吗?相关推荐

  1. @cacheable 设置过期时间_缓存面试三连击——聊聊Redis过期策略?内存淘汰机制?再手写一个LRU 吧!...

    大家好,今天我和大家想聊一聊有关redis的过期策略的话题. 听到这里你也许会觉得:"我去,我只是个日常搬砖的,这种偏底层的知识点,我需要care吗?" 话虽如此·,但是兄die, ...

  2. 字节跳动1/3员工不支持取消大小周!库克称iPhone将采用可回收材料生产;清华博士接亲被要求现场写代码|极客头条...

    「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧. 整理 | 梦依丹 出品 | CSDN(ID:CSDNnews ...

  3. 清华博士接亲被要求现场写代码,5 分钟做出一颗爱心樱花3D相册(HTML+CSS+JavaScript)

    清华博士接亲被要求现场写代码,5 分钟做出一颗爱心3D相册(HTML+CSS+JavaScript)「可以拿去送给自己喜欢的人」 告白前, 我们的留言区开放给你!快来秀出你的别样表白方式,和你心仪的那 ...

  4. 字节跳动1/3员工不支持取消大小周!库克称iPhone将采用可回收材料生产;清华博士接亲被要求现场写代码|极客头条

    一分钟速览新闻点! 字节跳动 1/3 员工不支持取消大小周!员工:每年少赚 10 万块 我国互联网视频用户突破 9 亿,20 岁以下网民达 1.6 亿 华为 Mate50 最新消息:不会停更,华为 P ...

  5. 腾讯三面(hr面)之轻松过关

    腾讯二面之细节大曝光中最后提到,22号晚上11:00先后分别收到邮件.短信与电话通知: 腾讯邀请你于4月23号16点到珞珈山国际酒店3楼2号会议室参加软件开发类面试,请携带简历提前5分钟到达.[腾讯科 ...

  6. 清华博士接亲被要求现场写代码,5 分钟做出一颗爱心樱花3D相册(HTML+CSS+JavaScript)...

    ❉ 清华博士接亲被要求现场写代码,5 分钟做出一颗爱心3D相册(HTML+CSS+JavaScript)「可以拿去送给自己喜欢的人」 告白前, 我们的留言区开放给你!快来秀出你的别样表白方式,和你心仪 ...

  7. 腾讯三面:说说前端监控平台/监控SDK的架构设计和难点亮点?

    点击上方 前端Q,关注公众号 回复加群,加入前端Q技术交流群 前言 事情是这样的,上周,我的一位两年前端经验的发小,在 腾讯三轮面试 的时候被问了一个问题:说说你们公司前端监控项目的架构设计和亮点设计 ...

  8. 清华博士接亲被要求现场写代码,网友:真是面向对象编程!

    源 / 顶级程序员 因为结婚,程序员又上热搜了 但这次不是加班维护服务器 而是程序员接亲被要求现场写代码 01 清华计算机博士 接亲被要求现场写代码 陕西西安,一位清华计算机博士接亲,被新娘要求当场写 ...

  9. 清华博士接亲被要求现场写代码,5 分钟做出一颗爱心

    来源:CSDN本文约1000字,建议阅读5分钟 以后接亲没点能力,怕是成功不了. 6月20日,话题#清华博士接亲被要求现场写代码#冲上微博热搜,有网友直呼:以后接亲没点能力,怕是成功不了,不得不说,有 ...

最新文章

  1. PHP服务器时间差8小时解决方案
  2. SQL Server :DBLINK创建及使用
  3. 庆祝杭州移动电视诞生一周年
  4. 设计模式--简单工厂VS工厂VS抽象工厂
  5. 如何预防淋巴癌?(希望分享给更多的人看)
  6. 【Python学习】 - 超详细的零基础Pandas学习(附Python数据分析与应用课本第四章实训答案)
  7. PS如何对JPG文件直接抠图
  8. 4x3矩阵键盘扫描法c语言,四种4*4矩阵键盘的扫描方法
  9. 很多人在销售过程中,喜欢考虑用一些话术
  10. 颜色直方图的计算、显示、处理、对比及反向投影(How to Use Histogram? Calculate, Show, Process, Compare and BackProject)...
  11. 第八章 虚拟机字节码执行引擎
  12. [软件更新]迅雷v5.9.8.1084发布
  13. keil4注册机注册不了怎么办?我已经试过下面的注册机不行。求各大神指教一下?
  14. SPC 之 I-MR 控制图
  15. 模拟钟表的手机软件_手机时钟软件推荐
  16. eclipse运行java快捷键_Eclipse快捷键【常用】
  17. 写在前面(ShenYu)
  18. 2021-06-05按键精灵实现远程获取消息内容
  19. 【Camera】通过查看位置方向的平面进行灵活的相机校准
  20. 最牛逼的心理学家是如何看待人生的

热门文章

  1. python练习_Python随笔31:Python基础编程练习题27~28
  2. JavaScript初学者编程题(22)
  3. 关于TypeError: ‘function‘ object is not subscriptable 报错的解决方法
  4. PTA基础编程题目集-6-3 简单求和
  5. esxi vsphere的端口_vSphere Client 6.0 更改 ESXESXi 主机的端口
  6. 电大计算机应用基础上机考试1200,电大《计算机应用基础上机考试操作题
  7. 从严把控_精准把控 推动从严治党走深走实
  8. CF5A Chat Server's Outgoing Traffic(字符串模拟,find函数的应用)难度⭐
  9. JavaScript与Java函数的比较
  10. 吴恩达机器学习笔记31-梯度检验(Gradient Checking)