Title

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) -:如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥已经存在,则变更其数据值;如果密钥不存在,则插入该组「密钥/数据值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:

你是否可以在 O(1) 时间复杂度内完成这两种操作?

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

Solve

自带数据结构:

这尼玛好熟悉啊,Python里不是有一种结合了哈希表和双链表的数据结构叫OrderedDict么,几行代码就能解决战斗。

class LRUCache(collections.OrderedDict):def __init__(self, capacity: int):super().__init__()self.capacity = capacitydef get(self, key: int) -> int:if key not in self:return -1self.move_to_end(key=key)return self[key]def put(self, key: int, value: int) -> None:if key in self:self.move_to_end(key=key)self[key] = valueif len(self) > self.capacity:self.popitem(last=False)

但显然在面试的时候这不是面试官想要的结果,因此我们还是用哈希表+双链表维护一个数据结构吧。


哈希表+双链表:

LRU 缓存机制可以通过哈希表辅以双链表维护所有在缓存中的键值对。

  • 双链表按照被使用的顺序存储键值对,靠近头部的键值对是最近被使用的,靠近尾部的键值对是最久没被使用过的。
  • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双链表中的位置。

这样以来,首先使用哈希表进行定位,找出缓存项在双链表中的位置,随后将其移动到双链表的头部,即可在O(1)的时间内完成get或者put操作。

具体的方法如下:

  • 对于get操作,首先判断key是否存在:

    • 如果key不存在,返回-1;
    • 如果key存在,则key对应的节点是最近被使用的节点,通过哈希表定位到该节点在双链表中的位置并将其移动到双链表的头部,最后返回该节点的值。
  • 对于post操作,首先判断key是否存在:

    • 如果key不存在,使用keyvalue创建一个新的节点,在双链表的头部添加该节点,并将key和该节点添加到哈希表中,然后判断双链表的节点数是否超出容量:

      • 如果超出容量,删除双链表的尾部节点,并删除哈希表中对应的项
    • 如果key存在,则与get操作类似,先通过哈希表定位,再将对应的节点的值更新为value,并将该节点移动到双链表的头部。

上述各项操作中,访问哈希表的时间复杂度为 O(1),在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)。而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1) 时间内完成。

小贴士

在双向链表的实现中,使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。

复杂度分析

时间复杂度:对于 put 和 get 都是 O(1)。

空间复杂度:O(capacity),因为哈希表和双向链表最多存储 capacity+1 个元素。

Code

class DoubleLinkNode:def __init__(self, key=0, value=0):self.key, self.value, self.prev, self.next = key, value, None, Noneclass LRUCache:def __init__(self, capacity: int):self.cache, self.capacity, self.size = dict(), capacity, 0self.head, self.tail = DoubleLinkNode(), DoubleLinkNode()self.head.next, self.tail.prev = self.tail, self.headdef get(self, key: int) -> int:if key not in self.cache:return -1# 如果 key 存在,先通过哈希表定位,再移到头部node = self.cache[key]self.moveToHead(node)return node.valuedef put(self, key: int, value: int) -> None:if key not in self.cache:# 如果 key 不存在,创建一个新的节点node = DoubleLinkNode(key, value)# 添加进哈希表self.cache[key] = node# 添加至双向链表的头部self.addToHead(node)self.size += 1if self.size > self.capacity:# 如果超出容量,删除双向链表的尾部节点removed = self.removeTail()# 删除哈希表中对应的项self.cache.pop(removed.key)self.size -= 1else:# 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部node = self.cache[key]node.value = valueself.moveToHead(node)def addToHead(self, node):node.prev = self.headnode.next = self.head.nextself.head.next.prev = nodeself.head.next = nodedef removeNode(self, node):node.prev.next = node.nextnode.next.prev = node.prevdef moveToHead(self, node):self.removeNode(node)self.addToHead(node)def removeTail(self):node = self.tail.prevself.removeNode(node)return node

146. LRU Cache相关推荐

  1. 【重点】LeetCode 146. LRU Cache

    LeetCode 146. LRU Cache 1.Solution1 本博客转载自:http://www.cnblogs.com/grandyang/p/4587511.html 这道题让我们实现一 ...

  2. 【hard】146. LRU Cache

    其实也米有很难--只是c++11的api这么好用的吗 Design and implement a data structure for Least Recently Used (LRU) cache ...

  3. linux cache lru回收,LRU cache 算法

    上周末同学问了一些操作系统的问题,涉及到LRU cache,顺便复习了一下. LRU是least recently used的缩写,意思是最近最少使用,是一种内存页面置换算法.根据程序设计局部性的原则 ...

  4. 146. LRU 缓存机制

    146. LRU 缓存机制 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 . 实现 LRUCache 类: LRUCache(int capacity) 以正整数作为容量 ...

  5. Java实现 LeetCode 146 LRU缓存机制

    146. LRU缓存机制 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - ...

  6. Leetcode 146. LRU缓存机制【哈希表 [哈希表存储每个元素在双向链表中的指针]+双向链表】

    文章目录 问题描述 解题报告 实验代码 参考资料 问题描述 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . ...

  7. 单机 “5千万以上“ 工业级 LRU cache 实现

    文章目录 前言 工业级 LRU Cache 1. 基本架构 2. 基本操作 2.1 insert 操作 2.2 高并发下 insert 的一致性/性能 保证 2.3 Lookup操作 2.4 shar ...

  8. 代码写对了还挂了?程序媛小姐姐从 LRU Cache 带你看面试的本质

    来源 | 码农田小齐 责编 |  Carol 前言 在讲这道题之前,我想先聊聊「技术面试究竟是在考什么」这个问题. 技术面试究竟在考什么 在人人都知道刷题的今天,面试官也都知道大家会刷题准备面试,代码 ...

  9. 从 LRU Cache 带你看面试的本质

    前言 在讲这道题之前,我想先聊聊「技术面试究竟是在考什么」这个问题. 技术面试究竟在考什么 在人人都知道刷题的今天,面试官也都知道大家会刷题准备面试,代码大家都会写,那面试为什么还在考这些题?那为什么 ...

最新文章

  1. Revit二次开发之“选择某一楼层的墙”
  2. .NET新手系列(六)
  3. c语言图形化编程入门_C语言C++新手入门,VS2013编程器安装教程
  4. Product not scheduled in sales organization XXX,distribution channel
  5. html留言回复评论页面模板,HTML5实现留言和回复的页面样式
  6. 真格量化——中性策略交易期权
  7. SecureCRT突然卡死的问题
  8. Python3 离线安装第三方包
  9. 【Elasticsearch】Elasticsearch 集群健康值红色 解决方案 或者 分片 未分配
  10. HDU-1874畅通工程续( 最短路)
  11. dwr java有返回值但是js获取不到返回值_一探究竟:Java反射效率低的原因到底在哪?...
  12. Druid.jar包
  13. cad上样条曲线上的点太多了_CAD样条曲线添加控制点
  14. JS 模拟手机页面文件的下拉刷新
  15. 遍历目录下的所有文件和文件夹
  16. spring boot 尚桂谷学习笔记05 ---Web
  17. 数码相机自动检测与自动曝光
  18. 数据库视图和索引基本知识
  19. string morphing 巧妙dp+dfs
  20. b站大佬稚晖君的首次直播分享学习

热门文章

  1. 3 - SQL Server 2008 之 使用SQL语句删除约束条件
  2. 分块读取Blob字段数据(Oracle)
  3. 转:给自己TopCoder SRM的建议
  4. 解决PHP下载文件名中文乱码
  5. hssfworkbook 单元格合并后宽度不生效_Excel表格“假”合并,有多牛?
  6. 宝塔网设置伪静态进行隐藏php后缀名,nextcloud宝塔面板nginx伪静态-去除index.php
  7. thymeleaf 中文_springboot 整合 thymeleaf(上手即用)
  8. java代码走读,WebRTCDemo.apk代码走读(一):初始化
  9. Java黑皮书课后题第10章:*10.15(几何:边框)边框是指包围一个二维平面上点集的最小矩形,编写一个方法,为二维平面上一系列点返回一个边框
  10. 【2012百度之星/资格赛】H:用户请求中的品牌