Leetcode 146 LRU缓存 的三种解法

146. LRU 缓存

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多调用 2 * 105 次 get 和 put

解法1:Map + ArrayList (key)

用一个Map来存放key和value,一个ArrayList来存放访问顺序。
当用户get、put的key存在时,则从ArrayList中找到对应的key删除,然后把新访问的key放到尾部。

头部表示最久未访问的数据,尾部表示最新访问的数据。与数据结构匹配。

class LRUCache {private int capacity;private Map<Integer,Integer> cache;private List<Integer> delete;public LRUCache(int capacity) {this.cache = new HashMap<>(capacity);this.capacity = capacity;this.delete = new ArrayList<>(capacity);}public int get(int key) {Integer value = cache.get(key);if(value != null){// update to delete queuedelete.remove((Integer)key);delete.add((Integer)key);return value;}return -1;}public void put(int key, int value) {// if existedif (cache.containsKey(key)) {// update to delete queuedelete.remove((Integer)key);delete.add((Integer)key);cache.put(key, value);return;}// if cache is full, need to deleteif(cache.size() == capacity){Integer deleteKey = delete.remove(0);cache.remove(deleteKey);}cache.put(key, value);delete.add(key);}}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/

 

性能不怎么好,花了883ms。大部分时间花在ArrayList中查找key的位置和删除key后移动元素上了。

那我们想办法改进下,查找和删除。

解法2:Map + ArrayList (key, timestamp)

我们看到解法1中大部分时间都是在ArrayList中查找key和删除后移动数据。那么我们能不能不移动数据?答案是可以的。

方法是:我们每次操作key后为其引入一个timestamp,需要删除这个key时,比较待删除的timestamp是否没有更新过,没更新过则删除。这里使用程序启动后的纳秒,防止在同一个微秒内完成了多个操作,不能区分。

class LRUCache {private int capacity;private Map<Integer,KeyTime> cache;private List<KeyTime> delete;public LRUCache(int capacity) {this.capacity = capacity;this.cache = new HashMap<>(capacity*3/2);this.delete = new LinkedList<>();}public int get(int key) {KeyTime kt = cache.get(key);if(kt != null){long time = System.nanoTime();kt.time = time;// update to delete queuedelete.add(new KeyTime(key, kt.value, time));return kt.value;}return -1;}public void put(int key, int value) {long time = System.nanoTime();// if existedKeyTime kt = cache.get(key);if (kt != null) {kt.time = time;kt.value = value;// update to delete queuedelete.add(new KeyTime(key, value, time));return;}// if cache is full, need to deleteif(cache.size() == capacity){while(true) {KeyTime deleteKt = delete.remove(0);KeyTime existKt = cache.get(deleteKt.key);if (existKt != null && existKt.time == deleteKt.time) {cache.remove(deleteKt.key);break;}}}cache.put(key, new KeyTime(key, value, time));delete.add(new KeyTime(key, value, time));}static class KeyTime {int key;int value;long time;public KeyTime(int key, int value, long time) {this.key = key;this.value = value;this.time = time;}}}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/

性能已经从883ms提升到了52ms,历史性的飞跃。还不错,可惜才28.88%,要是还想进一步优化的话,可以参考官方的LinkedHashMap实现。
 

解法3:HashMap + 双向链表 = LinkedHashMap

这里get/put 后把新访问的节点移动到表尾处,表头存放的是最久未访问的数据。

具体实现如下:
 

public class LRUCache {/*** use double direction linked list to store the order which to evict over due key.*/private int capacity;private Map<Integer,Node> cache;// store the first node to delete which is unused for a long timeprivate Node head;// store the used node recentlyprivate Node tail;public LRUCache(int capacity) {this.capacity = capacity;this.cache = new HashMap<>(capacity);this.head = new Node(-1, 0, null, null);this.tail = new Node(-2, 0, head, null);this.head.next = tail;}public int get(int key) {Node node = cache.get(key);if (node != null) {// existed then move to headmoveToTail(node);return node.value;}return -1;}public void put(int key, int value) {Node node = cache.get(key);if (node != null) {// existed, only need to update positionmoveToTail(node);node.value = value;return;}// cache is full, get the last unused node to removeif (cache.size() == capacity) {// remove the head node which is unused for a long timeNode toDelete = head.next;deleteNode(toDelete);cache.remove(toDelete.key);}node = new Node(key, value, null, null);cache.put(key, node);addToTail(node);}static class Node {int key;int value;Node previous;Node next;public Node(int key, int value, Node previous, Node next) {this.key = key;this.value = value;this.previous = previous;this.next = next;}}public void moveToTail(Node node) {deleteNode(node);addToTail(node);}/*** delete node from list.*/public void deleteNode(Node node) {node.previous.next = node.next;node.next.previous = node.previous;}/*** add node to list tail.*/public void addToTail(Node node) {node.next = tail;node.previous = tail.previous;tail.previous.next = node;tail.previous = node;}  
}

运行时间43ms,超越98.98%,可以了。 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

尚硅谷 java 学习Day19 抽象类与抽象方法、接口、内部类

6-5 抽象类(abstract)与抽象方法&#xff08;important&#xff09; 一、什么叫抽象类&#xff1a; 有时候将一个父类设计的非常抽象&#xff0c;以至于它没有具体的实例&#xff0c;这样的类称为抽象类 abstract关键字的使用&#xff1a; ​ 1、abstract:抽象的 ​ 2、abs…

【LeetCode Hot100 链表(上)】相交链表、反转链表、回文链表、环形链表、合并两个有序链表、两数相加

链表 1. 相交链表问题描述解决思路代码实现 2. 反转链表问题描述解决思路代码实现 3. 回文链表问题描述解决思路代码实现 4. 环形链表问题描述解决思路代码实现 5. 环形链表II问题描述解决思路代码实现 6. 合并两个有序链表问题描述解决思路代码实现 7. 两数相加问题描述解决思…

【Python pro】基本数据类型

一、数字类型 1.1 数字类型的组成 1.1.1 整数 &#xff08;1&#xff09;十进制&#xff0c;二进制0b&#xff0c;八进制0o&#xff0c;十六进制0x print(16 0b10000 0o20 0x10) # 输出&#xff1a;True&#xff08;2&#xff09;十进制转其他进制 a bin(16) b oct(1…

拯救者电脑在重装系统之后电源计划丢失Fn+Q切换不了模式怎么恢复?

参考联想知识库的一下链接&#xff1a; https://iknow.lenovo.com.cn/detail/196192 其中下载的解压文件后的文件需要复制粘贴到D盘的根目录下&#xff0c;再来运行文件。若在生成的log文件中看到导入成功以及控制面板中看到已添加的电源计划即可 如果还是无效可是试试以下的…

ubuntu 执行 sudo apt-get update 报错

记录一下&#xff0c;遇到这个问题了&#xff0c;网络上看到的解决办法&#xff0c;亲测有效 执行sudo apt-get update ,却报以下错误&#xff0c;“SECURITY: URL redirect target contains control characters rejecting ” 经检查发现&#xff0c;/etc/apt/source.list 下的…

深度集成DeepSeek大模型:WebSocket流式聊天实现

目录 5分钟快速接入DeepSeek大模型&#xff1a;WebSocket实时聊天指南创建应用开发后端代码 (Python/Node.js)结语 5分钟快速接入DeepSeek大模型&#xff1a;WebSocket实时聊天指南 创建应用 访问DeepSeek官网 前往 DeepSeek官网。如果还没有账号&#xff0c;需要先注册一个。…

java断点调试(debug)

在开发中&#xff0c;新手程序员在查找错误时, 这时老程序员就会温馨提示&#xff0c;可以用断点调试&#xff0c;一步一步的看源码执行的过程&#xff0c;从而发现错误所在。 重要提示: 断点调试过程是运行状态&#xff0c;是以对象的运行类型来执行的 断点调试介绍 断点调试是…

软件技术实训室解决方案(2025年最新版)

软件产业作为新兴产业的核心组成部分&#xff0c;是推动数字经济发展的重要力量。在“十四五”规划的新机遇与挑战下&#xff0c;我国已明确将加强关键数字技术创新应用作为战略重点&#xff0c;并将软件和信息技术服务业的发展列为重中之重。这不仅是为了加速构建现代产业体系…

foobar2000设置DSP使用教程及软件推荐

foobar2000安卓中文版&#xff1a;一款高品质手机音频播放器 foobar2000安卓中文版是一款备受好评的高品质手机音频播放器。 几乎支持所有的音频格式&#xff0c;包括 MP3、MP4、AAC、CD 音频等。不论是经典老歌还是最新的流行音乐&#xff0c;foobar2000都能完美播放。除此之…

DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地

对于个人开发者或尝鲜者而言&#xff0c;本地想要部署 DeepSeek 有很多种方案&#xff0c;但是一旦涉及到企业级部署&#xff0c;则步骤将会繁琐很多。 比如我们的第一步就需要先根据实际业务场景评估出我们到底需要部署什么规格的模型&#xff0c;以及我们所要部署的模型&…

I2C、SPI、UART

I2C&#xff1a;串口通信&#xff0c;同步&#xff0c;半双工&#xff0c;双线&#xff08;数据线SDA时钟线SCL&#xff09;&#xff0c;最大距离1米到几米 SPI&#xff08;串行外设接口&#xff09;&#xff1a;串口通信&#xff0c;同步&#xff0c;全双工&#xff0c;四线&…

uniapp 连接mqtt

1&#xff1a;下载插件 npm install mqtt 2&#xff1a;创建 mqtt.js /* main.js 项目主入口注入实例 */ // import mqttTool from ./lib/mqttTool.js // Vue.prototype.$mqttTool mqttTool/* 使用范例见 /pages/index/index.vue */ // mqtt协议&#xff1a;H5使用ws/wss APP-…

shell脚本备份PostgreSQL数据库和库下表

注意&#xff1a; 以下为对PostgreSQL13.16版本数据库备份shell脚本参考请确认备份节点上psql和pgdump的版本不至于太低&#xff0c;建议>13.16该脚本目前是对于整库、&#xff08;默认针对public这个schema&#xff0c;如果有其他schema&#xff0c;请自行添加一层循环&am…

EXCEL解决IF函数“您已为此函数输入太多个参数”的报错

IF函数的基本结构是IF(条件, 值为真时的结果, 值为假时的结果)&#xff0c;所以标准的IF函数最多只能有三个参数。当用户输入的参数超过三个时&#xff0c;Excel就会报这个错误。比如多个IF语句叠加&#xff0c;但可能在嵌套的过程中没有正确关闭每个IF函数的括号&#xff0c;导…

图像质量评价指标-UCIQE-UIQM

一、评价指标UCIQE 在文章《An underwater color image quality evaluation metric》中&#xff0c;提到的了评价指标UCIQE&#xff08;Underwater Colour Image Quality Evaluation&#xff09;&#xff0c;是一种无参考图像质量评价指标&#xff0c;主要用于评估水下图像的质…

Vue 前端开发中的路由知识:从入门到精通

文章目录 引言1. Vue Router 简介1.1 安装 Vue Router1.2 配置 Vue Router1.3 在 Vue 实例中使用 Vue Router 2. 路由的基本用法2.1 路由映射2.2 路由视图2.3 路由链接 3. 动态路由3.1 动态路径参数3.2 访问动态参数3.3 响应路由参数的变化 4. 嵌套路由4.1 定义嵌套路由4.2 渲染…

基于Springboot+微信小程序调用文心一言大模型实现AI聊天

一、文章前言 此文主要实现基于Springboot微信小程序调用文心一言大模型实现AI聊天对话功能&#xff0c;使用Java作为后端语言进行支持&#xff0c;界面友好&#xff0c;开发简单。 二、开发流程及工具准备 2.1、登录百度智能云平台&#xff0c;获取 API Key 和 Secret Key两个…

leaflet前端初始化项目

1、通过npm安装leaflet包&#xff0c;或者直接在项目中引入leaflet.js库文件。 npm 安装&#xff1a;npm i leaflet 如果在index.html中引入leaflet.js,在项目中可以直接使用变量L. 注意:尽量要么使用npm包&#xff0c;要么使用leaflet.js库&#xff0c;两者一起使用容易发生…

Deepseek官网接口文档

API 接口 生成完成 生成聊天完成 创建模型 列出本地模型 显示模型信息 复制模型 删除模型 拉取模型 推送模型 生成嵌入 列出运行中的模型 版本 约定 模型名称 模型名称遵循 model:tag 格式&#xff0c;其中 model 可以有一个可选的命名空间&#xff0c;例如 ex…

容器运行常见数据库

一.涉及镜像压缩包 均为amd架构版本&#xff1a;mysql:5.7.42、postgres:13.16、dm8:20250206_rev257733_x86_rh6_64、oceanbase-ce:v4.0、opengauss:5.0.2 通过网盘分享的文件&#xff1a;db.tgz 链接: https://pan.baidu.com/s/1EBbFPZj1FxCA4_GxjVunWg?pwd563s 提取码: 5…