【牛客算法】某司面试算法题:设计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.mzph.cn/web/57314.shtml

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

相关文章

Java面试题集锦

1. 计算机网络 1、什么是计算机网络 2、什么是协议 3、什么是IP地址 4、什么是子网 5、什么是DNS 6、什么是NAT 7、什么是带宽和延迟 8、什么是VPN 9、路由器和交换机的区别 10、OSI与TCP/IP模型 11、TCP与UDP的区别 12、TCP三次握手四次挥手 13、HTTP和HTTPS的区…

快速在win11上利用hyper-v安装虚拟系统:遭遇Start PXE over ipv4

以前习惯使用VMware&#xff0c;但在win11上折腾了很久都遇到各种麻烦&#xff0c;索性就上微软自家的Hyper-V&#xff0c;。作为微软自家的产品&#xff0c;Hyper-V 与 Windows 11 操作系统紧密结合&#xff0c;具有良好的兼容性和稳定性。在安装和使用过程中&#xff0c;与系…

Python中的数据可视化:Matplotlib基础与高级技巧

Python中的数据可视化&#xff1a;Matplotlib基础与高级技巧 数据可视化是数据分析和数据科学中不可或缺的一部分。通过图表&#xff0c;我们可以更直观地观察数据的分布和趋势。Matplotlib作为Python最基础、也是最广泛使用的绘图库之一&#xff0c;不仅支持多种常用图表&…

沈阳乐晟睿浩科技有限公司抖音小店新篇章

在当今数字化时代&#xff0c;电商行业如雨后春笋般迅速崛起&#xff0c;其中抖音小店凭借其庞大的用户基础、精准的推荐算法和便捷的购物体验&#xff0c;成为了电商领域的新宠。在这场电商变革中&#xff0c;沈阳乐晟睿浩科技有限公司&#xff08;以下简称“乐晟睿浩”&#…

正则表达式:强大的文本匹配与处理工具

正则表达式&#xff1a;强大的文本匹配与处理工具 正则表达式&#xff08;Regular Expression&#xff0c;简称 regex&#xff09;是一种用于定义搜索模式的字符串&#xff0c;用于匹配和处理文本。它广泛应用于数据清洗、文本分析、日志分析等场景。本文将介绍正则表达式的基…

1,国产FPGA(紫光同创)-IP核-PLL

本文默认在0&#xff0c;国产FPGA&#xff08;紫光同创&#xff09;-新建PDS工程基础上完成。 1&#xff0c;添加IP核 右击&#xff08;1&#xff09;空白处进行添加&#xff0c;点击New IP&#xff08;2&#xff09;进行新建IP核。 选择本次实验要配置的IP核-PLL&#xff08;…

“智能二维码”实现光伏行业数字信息化管理

近日&#xff0c;为了提升管理效率&#xff0c;国电投建业光伏电站将二维码引入设备巡视和班组建设中。 首先&#xff0c;使用传统纸质巡视作业卡&#xff0c;巡视工作强度大&#xff0c;容易出现错误&#xff1b;此外&#xff0c;“三会一活动”和培训记录等班组建设过程材料大…

linux之awk

awk 是一个强大的文本处理工具&#xff0c;广泛用于Linux和Unix系统中。它可以用来处理和分析文本文件&#xff0c;尤其是那些以固定格式排列的数据。下面是一些简单的 awk 用法示例&#xff0c;帮助你更好地理解它的基本功能。 1. 打印文件的特定列 假设有一个CSV文件 data.…

电脑录屏不用愁!四款免费录屏软件深度体验分享

虽然我不是专业的&#xff0c;但是我有一颗想要变得专业的心。作为一名经常需要录制教学视频和游戏直播的博主&#xff0c;我深知一款好用的录屏软件对于工作效率的重要性。今天&#xff0c;我就来和大家分享一下我最近亲测的四款免费录屏软件&#xff0c;来看看哪一款更适合你…

shodan5,参数使用,批量查找Mongodb未授权登录,jenkins批量挖掘

查找美国安全局漏洞 nww.nsa.gov&#xff08;美国安全局官方网站) net参数使用 搜索指定的ip网段 shodan search --limit 10 --fields ip_str,port net:208.88.84.0/24 (老美国家安全局的一个网段)可能直接访问不太行&#xff0c;可以使用host参数&#xff0c;得到域名再去…

部署MiniCPM-V

GitHub - OpenBMB/MiniCPM-V: MiniCPM-V 2.6: A GPT-4V Level MLLM for Single Image, Multi Image and Video on Your Phone 安装和执行 "Local WebUI Demo" 的步骤如下&#xff1a; 克隆仓库并导航到源文件夹&#xff1a; git clone https://github.com/OpenBMB/M…

Vue 权限管理

vue 中&#xff0c;比较常见的需要进行权限管控的权限控制实现思路有四条&#xff1a;、 菜单的控制 在登录请求中&#xff0c;会得到权限数据&#xff0c;当然&#xff0c;这个需要后端返回数据的支持&#xff0c;前端根据权限数据&#xff0c;展示对应的菜单&#xff0c;单…

MongoDB 8.0.3版本安装教程

MongoDB 8.0.3版本安装教程 一、下载安装 1.进入官网 2.选择社区版 3.点击下载 4.下载完成后点击安装 5.同意协议&#xff0c;下一步 6.选择第二个Custon&#xff0c;自定义安装 7.选择安装路径 &#xff01;记住安装路径 8.默认&#xff0c;下一步 9.取…

用于约束多目标优化的新型双阶段双种群进化算法

PPT链接&#xff1a;人工智能论文课程汇报介绍PPT资源-CSDN文库 A Novel Dual-Stage Dual-Population Evolutionary Algorithm for Constrained Multi-Objective Optimization IEEE Transactions on Evolutionary Computation, Volume 26, Issue 5, Pages 1129-1143, October …

C语言中的位操作

第一章 变量某位赋值与连续赋值 寄存器 | 值 //例如&#xff1a;a 1000 0011b a | (1<<2) //a 1000 0111 b 单独赋值 a | (3<<2*2) // 1011 0011b 连续赋值 第二章 变量某位清零与连续清零 寄存器 & ~&#xff08;&#xff09; 值 //例子&#xff1a;a …

uniapp 报错Invalid Host header

前言 在本地使用 nginx 反向代理 uniapp 时&#xff0c;出现错误 Invalid Host header 错误原因 因项目对 hostname 进行检查&#xff0c;发现 hostname 不是预期的&#xff0c;所以&#xff0c;报错 Invalid Host header 。 解决办法 这样做是处于安全考虑。但&#xff0…

Ubuntu 20.04 安装 OpenCV 和 OpenCV_contrib 教程

Ubuntu 20.04 安装 OpenCV 和 OpenCV_contrib 教程 Ubuntu 20.04 安装 OpenCV 和 OpenCV_contrib 教程前言 OpenCV概述核心功能优势特点应用领域安装与使用 OpenCV_contrib概述核心功能具体模块 安装与使用一、准备工作二、下载OpenCV和OpenCV_contrib三、编译和安装OpenCV四、…

Spring MVC 知识点全解析

Spring MVC 知识点全解析 Spring MVC 是一个基于 Java 的请求驱动的 Web 框架&#xff0c;属于 Spring 框架的一部分&#xff0c;广泛用于构建企业级 Web 应用程序。本文将详细阐述 Spring MVC 的核心知识点&#xff0c;包括其工作原理、关键组件、配置、请求处理、数据绑定、…

10.24.2024刷华为OD C题型(四) -- 对象list按照多个属性排序

文章目录 最长连续子序列AI面板识别语法知识记录 最长连续子序列 https://www.nowcoder.com/discuss/592408743019589632 if __name__ "__main__":# 获取用户输入# numbers int(input().split(,))# str_arr input().split(,)arr [int(num) for num in input(…

生活中是否害怕过机械硬盘出现坏道?

目录 一、坏道起因 二、继续了解-系统对坏扇区的处理 &#xff08;一&#xff09;硬盘自身的处理机制 &#xff08;二&#xff09;操作系统层面的处理 三、进一步了解-备用扇区 &#xff08;一&#xff09;备用扇区的工作原理 &#xff08;二&#xff09;S.M.A.R.T.技术…