【牛客算法】某司面试算法题:设计LRU缓存结构

一、算法题描述

1.1 算法描述

设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 capacity ,操作次数是 n ,并有如下功能:

  1. Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  2. get(key):如果关键字 key 存在于缓存中,则返回key对应的value值,否则返回 -1
  3. set(key, value):将记录(key, value)插入该结构,如果关键字 key 已经存在,则变更其数据值 value,如果不存在,则向缓存中插入该组 key-value ,如果key-value的数量超过capacity,弹出最久未使用的key-value

1.2 提示:

1.某个keysetget操作一旦发生,则认为这个key的记录成了最常使用的,然后都会刷新缓存
2.当缓存的大小超过capacity时,移除最不经常使用的记录
3.返回的value都以字符串形式表达,如果是set,则会输出"null"来表示(不需要用户返回,系统会自动输出),方便观察
4.函数setget必须以O(1)的方式运行
5.为了方便区分缓存里keyvalue,下面说明的缓存里key""号包裹

数据范围:

  • 1≤capacity<=10^5
  • 0≤key,val≤2×10^9
  • 1≤n≤10^5

1.3 示例

输入

["set","set","get","set","get","set","get","get","get"],[[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]],2

输出

["null","null","1","null","-1","null","-1","3","4"]

说明

我们将缓存看成一个队列,最后一个参数为2代表capacity,所以
Solution s = new Solution(2);
s.set(1,1); 		//将(1,1)插入缓存,缓存是{"1"=1},set操作返回"null"
s.set(2,2); 		//将(2,2)插入缓存,缓存是{"2"=2,"1"=1},set操作返回"null"
output=s.get(1);	// 因为get(1)操作,缓存更新,缓存是{"1"=1,"2"=2},get操作返回"1"
s.set(3,3); 		//将(3,3)插入缓存,缓存容量是2,故去掉某尾的key-value,缓存是{"3"=3,"1"=1},set操作返回"null" 
output=s.get(2);	// 因为get(2)操作,不存在对应的key,故get操作返回"-1"
s.set(4,4); 		//将(4,4)插入缓存,缓存容量是2,故去掉某尾的key-value,缓存是{"4"=4,"3"=3},set操作返回"null" 
output=s.get(1);	// 因为get(1)操作,不存在对应的key,故get操作返回"-1"
output=s.get(3);	//因为get(3)操作,缓存更新,缓存是{"3"=3,"4"=4},get操作返回"3"
output=s.get(4);	//因为get(4)操作,缓存更新,缓存是{"4"=4,"3"=3},get操作返回"4"        

1.4 提供的代码

import java.util.*;public class Solution {public Solution(int capacity) {// write code here}public int get(int key) {// write code here}public void set(int key, int value) {// write code here}
}/*** Your Solution object will be instantiated and called as such:* Solution solution = new Solution(capacity);* int output = solution.get(key);* solution.set(key,value);*/

二、算法实现

这是典型的 LRU 缓存问题,可以使用 HashMap双向链表 来实现,以保证 getset 操作都在 O(1)时间复杂度内完成。

2.1 解题思路

  1. 使用 HashMap:将 key 映射到对应的节点,这样可以在 O(1) 时间内查找元素。
  2. 使用双向链表:保存缓存的顺序,最久未使用的元素在链表的尾部,最近使用的在头部。
    • 每次访问 getset,将对应节点移动到链表头部。
    • 当缓存超出容量 capacity 时,移除链表尾部节点(最久未使用)。

2.2 实现细节

  • 构造函数 Solution(int capacity):初始化 capacity,以及 HashMap 和双向链表的头尾哨兵节点(dummy nodes),方便处理边界条件。
  • 方法 get(int key):如果 key 存在,则将该节点移动到链表头部并返回其 value;否则返回 -1
  • 方法 set(int key, int value):如果 key 已存在,则更新其 value 并移动到链表头部;否则,插入新节点到头部。若超过容量,则移除链表尾部节点。

2.3 代码实现

代码实现如下:

import java.util.HashMap;public class Solution {// 定义双向链表节点private class Node {int key, value; // 键值对Node prev, next; // 前后指针指向前后节点Node(int k, int v) { // 构造方法this.key = k;this.value = v;}}private int capacity; // LRU 缓存的最大容量private HashMap<Integer, Node> map; // 存储键值对<key, Node> 的映射,用于O(1)访问private Node head, tail; // 双向链表的哨兵头节点和尾节点public Solution(int capacity) {this.capacity = capacity; // 初始化缓存的容量this.map = new HashMap<>(); // 初始化哈希表// 初始化双向链表的哨兵节点(不存储实际数据,方便边界操作)this.head = new Node(0, 0); // 头哨兵节点this.tail = new Node(0, 0); // 尾哨兵节点head.next = tail; // 初始化链表,将头尾哨兵节点相连tail.prev = head;}// 获取缓存中指定键的值public int get(int key) {if (map.containsKey(key)) { // 检查键是否存在Node node = map.get(key); // 获取对应节点moveToHead(node); // 将该节点移到链表头部,标记为最近使用return node.value; // 返回对应的值}return -1; // 不存在则返回 -1}// 插入或更新缓存中的键值对public void set(int key, int value) {if (map.containsKey(key)) { // 如果键已存在Node node = map.get(key); // 获取已有节点node.value = value; // 更新节点的值moveToHead(node); // 将节点移到链表头部,标记为最近使用} else {// 若键不存在,则创建新节点Node newNode = new Node(key, value);map.put(key, newNode); // 将新节点加入哈希表addNode(newNode); // 插入新节点到链表头部// 如果超过容量,移除最久未使用节点(链表尾部节点)if (map.size() > capacity) {Node tail = removeTail(); // 删除尾部节点map.remove(tail.key); // 从哈希表中移除对应的键}}}// 辅助方法:在链表头部添加节点private void addNode(Node node) {node.next = head.next; // 将新节点的 next 指向 head 的 nextnode.prev = head; // 新节点的 prev 指向 headhead.next.prev = node; // 将 head 原来的 next 的 prev 指向新节点head.next = node; // head 的 next 指向新节点}// 辅助方法:从链表中移除节点private void removeNode(Node node) {node.prev.next = node.next; // 将 node 的前节点的 next 指向 node 的 nextnode.next.prev = node.prev; // 将 node 的后节点的 prev 指向 node 的 prev}// 辅助方法:将节点移动到链表头部(表示最近使用)private void moveToHead(Node node) {removeNode(node); // 先从链表中删除节点addNode(node); // 然后再添加到链表头部}// 辅助方法:移除链表尾部节点并返回该节点(最久未使用节点)private Node removeTail() {Node res = tail.prev; // 获取尾节点前的节点removeNode(res); // 从链表中删除return res; // 返回被移除的节点}
}

复杂度分析

  • 时间复杂度getset 操作都是 O(1),因为 HashMap 查找、插入和双向链表操作都在常数时间内完成。
  • 空间复杂度O(capacity),用于存储 HashMap 和双向链表中的节点。

代码详解

  1. Node

    • Node 是双向链表的节点类,包含 keyvalue,以及 prevnext 指针用于双向链接。
  2. 构造方法 Solution(int capacity)

    • 初始化 capacitymap,并创建链表的 headtail 哨兵节点。
    • 通过将 head.next 指向 tailtail.prev 指向 head,形成一个空的双向链表(仅有哨兵节点)。
  3. 方法 get(int key)

    • 检查 key 是否在 map 中。
    • 如果在,则通过 moveToHead(node) 将对应节点移到链表头部,以标记该节点为最近使用。
    • 返回节点的 value
    • 如果 key 不在 map 中,则返回 -1
  4. 方法 set(int key, int value)

    • 检查 key 是否在 map 中。
      • 如果 key 已存在,更新其 value,并调用 moveToHead(node) 将节点移到链表头部。
      • 如果 key 不存在,创建新节点,并插入到链表头部。
      • 检查 map 的大小是否超过 capacity,若超过则调用 removeTail() 删除链表尾部节点,并从 map 中移除对应键。
  5. 辅助方法

    • addNode(Node node):将新节点插入到链表头部,链表更新顺序为 head <-> node <-> head.next
    • removeNode(Node node):从链表中删除指定节点。
    • moveToHead(Node node):将节点先删除,再插入链表头部,更新为最近使用。
    • removeTail():删除链表尾部节点(最久未使用节点),并返回该节点。

关键点总结

  • HashMap 作为缓存记录的快速查找结构,使得 getset 操作在 O(1) 时间完成。
  • 双向链表 维护 LRU 顺序,头部表示最近使用,尾部表示最久未使用。
  • 容量管理:当超出容量时,通过 removeTail() 删除尾节点(最久未使用),并从 map 中移除。

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

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

相关文章

短信验证码发送实现(详细教程)

短信验证码 接口防刷强检验以及缓存验证码阿里云短信服务操作步骤验证码发送实现 好久没发文啦&#xff01;最近也是在工作中遇到我自认为需要记录笔记的需求&#xff0c;本人只求日后回顾有迹可寻&#xff0c;不喜勿喷&#xff01; 废话不多说&#xff0c;直接上代码&#xff…

深度学习数学基础之梯度

深度学习数学基础之梯度 方向余弦 方向导数 梯度&#xff08;向量&#xff09; 变化率最大的方向或者说方向导数最大的方向就是梯度向量的方向指向方向导数变化最大的方向

PYNQ 框架 - VDMA驱动 - 帧缓存

目录 1. 简介 2. 代码分析 2.1 _FrameCache 类定义 2.1.1 xlnk.cma_array() 2.1.2 pointerNone 2.1.3 PynqBuffer 2.2 _FrameCache 例化与调用 2.3 _FrameCache 测试 2.4 _FrameList 类定义 2.5 _FrameList 例化与调用 2.6 _FrameList 测试 3. 帧的使用 3.1 读取帧…

Cloud Compare学习笔记

1.1 导出文件 导出点云数据为 PCD 格式时&#xff0c;系统提供了三种保存选项&#xff0c;分别是 Compressed Binary&#xff08;压缩二进制&#xff09;、Binary&#xff08;二进制&#xff09;、ASCII/Text&#xff08;文本&#xff09; Compressed Binary&#xff08;压缩…

电商直播带货乱象频出,食品经销商如何规避高额损失?

近年来&#xff0c;电商直播带货乱象频出&#xff0c;食品经销行业售卖商品涉嫌违规的事件层出不穷。以食品安全为例&#xff0c;2024年10月17日市场监管总局发布了关于11批次食品抽检不合格情况的通告&#xff0c;在抽检的650批次样品中&#xff0c;发现存在食品添加剂超范围超…

攻防世界 MISC miao~详解

下载压缩包&#xff0c;但是尝试解压的时候提示错误&#xff0c;刚开始以为是伪加密之类的&#xff0c;但是尝试了一圈之后&#xff0c;发现并没有问题。后面用bandizip打开&#xff0c;得到了一张图片&#xff1a; 拖到010editor里面查看&#xff0c;没有发现什么 于是用随波逐…

基于Unet卷积神经网络的脑肿瘤MRI分割

项目源码获取方式见文章末尾&#xff01; 回复暗号&#xff1a;13&#xff0c;免费获取600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【YOLO模型实现农作物病虫害虫识别带GUI界面】 2.【卫星图像道路检测DeepLabV3P…

记一次:使用使用Dbeaver连接Clickhouse

前言&#xff1a;使用了navicat连接了clickhouse我感觉不太好用&#xff0c;就整理了一下dbeaver连接 0、使用Navicat连接clickhouse 测试连接 但是不能双击打开&#xff0c;可是使用命令页界面&#xff0c;右键命令页界面&#xff0c;然后可以用sql去测试 但是不太好用&#…

python nan是什么

NaN&#xff08;not a number&#xff09;&#xff0c;在数学表示上表示一个无法表示的数&#xff0c;这里一般还会有另一个表述inf&#xff0c;inf和nan的不同在于&#xff0c;inf是一个超过浮点表示范围的浮点数&#xff08;其本质仍然是一个数&#xff0c;只是他无穷大&…

ABAP开发学习——内存管理二

SAP内存与ABAP内存的不同 SAP内存 当在某个事务程序中输入了物料号等&#xff0c;在打开其他需要输入物料号的事务窗口中会自动带出&#xff0c;不需要自己输入&#xff0c;因为这些地方使用相同的parameter id&#xff0c;共享相同SAP内存区域 在数据库表TPARA中可以查看到 S…

如何在短时间内入门并掌握深度学习?

如何在短时间内快速入门并掌握深度学习&#xff0c;是很多读者的困惑——晦涩难懂的数学 知识、复杂的算法、烦琐的编程……深度学习虽然让无数读者心怀向往&#xff0c;却也让不少人望而生畏&#xff0c;深感沮丧&#xff1a;时间没少花&#xff0c;却收效甚微。 如何才能更好…

ubuntu交叉编译zlib库给arm平台使用

1.下载并解压: 2.生成makefile 3.修改makefile 4.编译: make 出现下面错误先安装 gcc-arm-linux-gnueabihf 安装 gcc-arm-linux-gnueabihf

MySQL数据类型——针对实习面试

目录 MySQL字段类型分类char和varchar的区别null和“ ”的区别datetime和timestamp的区别为什么在MySQL中不推荐使用text或blob类型MySQL中如何表示布尔类型在设计数据库中&#xff0c;如何优化性能&#xff08;一般不会问那么深&#xff0c;了解就行&#xff09; MySQL字段类型…

【有啥问啥】视频插帧算法技术原理详解

视频插帧算法技术原理详解 引言 视频插帧&#xff08;Video Interpolation&#xff09;技术&#xff0c;作为计算机视觉领域的一项重要应用&#xff0c;旨在通过算法手段在已有的视频帧之间插入额外的帧&#xff0c;从而提升视频的帧率&#xff0c;使其看起来更加流畅。这一技…

我在命令行下学日语

同一个动作重复 300 遍&#xff0c;肌肉就会有记忆&#xff0c;重复 600 遍&#xff0c;脊柱就会有记忆&#xff0c;学完五十音图不熟练&#xff0c;经常遗忘或者要好几秒才想得起来一个怎么办&#xff1f;没关系&#xff0c;我做了个命令行下的小游戏 KanaQuiz 来帮助你记忆&a…

开源一个开发的聊天应用与AI开发框架,集成 ChatGPT,支持私有部署的源码

大家好&#xff0c;我是一颗甜苞谷&#xff0c;今天分享一个开发的聊天应用与AI开发框架&#xff0c;集成 ChatGPT&#xff0c;支持私有部署的源码。 介绍 当前系统集成了ChatGPT的聊天应用&#xff0c;不仅提供了基本的即时通讯功能&#xff0c;还引入了先进的AI技术&#x…

【C++滑动窗口】2653. 滑动子数组的美丽值|1785

本文涉及的基础知识点 C算法&#xff1a;滑动窗口及双指针总结 C堆(优先队列) LeetCode2653. 滑动子数组的美丽值 给你一个长度为 n 的整数数组 nums &#xff0c;请你求出每个长度为 k 的子数组的 美丽值 。 一个子数组的 美丽值 定义为&#xff1a;如果子数组中第 x 小整数…

HarmonyOS NEXT: 抓住机遇,博

鸿蒙生态崛起&#xff1a;开发者如何抓住机遇&#xff0c;创造卓越应用体验 鸿蒙系统的崛起与优势开发者面临的机遇与挑战解决方案与前景分析开发人员学习路径 在移动操作系统领域&#xff0c;安卓&#xff08;Android&#xff09;和苹果iOS系统长期占据主导地位。然而&#xf…

django5入门【04】Django框架配置文件说明:settings.py

文章目录 1. 基础路径配置2. 启动模式配置3. 站点访问权限配置4. App配置5. 中间件配置6. 模板配置7. 数据库配置8. 路由配置9. 语言与时区配置10. 静态文件配置11. 总结 1. 基础路径配置 在settings.py文件中&#xff0c;通过BASE_DIR配置项来绑定项目的绝对路径。这个路径是…

ZeroNL2SQL:零样本 NL2SQL

发布于&#xff1a;2024 年 10 月 30 日 星期三 #RAG #NL2SQL # Zero-Shot 自然语言到 SQL&#xff08;NL2SQL&#xff09;的转换是一个重要的研究领域&#xff0c;它允许非技术用户轻松访问和分析数据&#xff0c;在商业智能、数据分析等领域具有广泛的应用前景。然而&#x…