面试遇到算法题:实现LRU缓存

news2024/11/18 1:36:41

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

这是一道大厂面试高频出现的算法题,难度为⭐️⭐️⭐️,属于中等,老铁们来一起看看这个题该怎么解?

1. 原题再现

没有废话,翠花,上酸菜!

2. 具体实现

为了实现一个满足 LRU (最近最少使用)缓存约束的数据结构,我们需要一个能够快速访问、更新和删除的数据结构,V 哥想到了用哈希表,因为哈希表提供了快速的查找能力,但是它不能保持元素的顺序;而双向链表可以保持元素的顺序,并且可以在 O(1) 时间复杂度内进行插入和删除操作。因此, V哥采用结合使用哈希表和双向链表来实现LRU缓存。

以下是 Java 代码实现:

import java.util.HashMap;

class Node {
    int key;
    int value;
    Node prev;
    Node next;

    public Node(int key, int value) {
        this.key = key;
        this.value = value;
    }
}

public class LRUCache {
    private HashMap<Integer, Node> cache;
    private Node head, tail;
    private int capacity;
    private int count;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>();
        this.head = new Node(0, 0);
        this.tail = new Node(0, 0);
        this.head.next = this.tail;
        this.tail.prev = this.head;
        this.count = 0;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) {
            Node newNode = new Node(key, value);
            cache.put(key, newNode);
            addNode(newNode);
            count++;
            if (count > capacity) {
                Node toDel = popTail();
                cache.remove(toDel.key);
                count--;
            }
        } else {
            node.value = value;
            moveToHead(node);
        }
    }

    private void addNode(Node node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(Node node) {
        removeNode(node);
        addNode(node);
    }

    private Node popTail() {
        Node res = tail.prev;
        removeNode(res);
        return res;
    }
}

使用示例:

public class Main {
    public static void main(String[] args) {
        LRUCache lRUCache = new LRUCache(2);
        lRUCache.put(1, 1); // 缓存是 {1=1}
        lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
        System.out.println(lRUCache.get(1));    // 返回 1
        lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
        System.out.println(lRUCache.get(2));    // 返回 -1 (未找到)
        lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
        System.out.println(lRUCache.get(1));    // 返回 -1 (未找到)
        System.out.println(lRUCache.get(3));    // 返回 3
        System.out.println(lRUCache.get(4));    // 返回 4
    }
}

输出结果应该与题目中的示例输出一致。这个实现使用了哈希表来快速查找节点,同时使用双向链表来维护节点的顺序和实现快速插入和删除操作。这样,我们就可以在 O(1) 的时间复杂度内完成 get 和 put 操作。

实现过程和步骤如下:

  1. 定义一个 Node 类,用于双向链表的节点,包含 key、value 以及前驱和后继节点的引用。

  2. 在 LRUCache 类中,定义一个 capacity 表示缓存的最大容量,一个 count 表示当前缓存中的节点数量,一个 cache 哈希表用于存储键和节点的映射,以及 head 和 tail 虚拟节点用于简化双向链表的操作。

  3. 实现 get 方法,首先在哈希表中查找键对应的节点,如果节点不存在,返回 -1。如果节点存在,则将该节点移动到链表的头部,表示最近被使用,然后返回节点的值。

  4. 实现 put 方法,如果键在哈希表中不存在,创建一个新的节点,并将其添加到链表的头部,同时添加到哈希表中。如果插入后节点数量超过了容量,则需要移除链表尾部的节点,并从哈希表中删除对应的条目。如果键已经存在,则更新对应的节点值,并将其移动到链表头部。

  5. addNode 方法用于将节点添加到链表头部,removeNode 方法用于从链表中移除节点,moveToHead 方法用于将节点移动到链表头部,popTail 方法用于移除链表尾部的节点。

  6. 在 Main 类的 main 方法中,我们创建了一个 LRUCache 实例,并执行了一系列的 put 和 get 操作来演示其功能。

V哥认为这个实现确保了 get 和 put 操作的平均时间复杂度为O(1)。get 操作直接通过哈希表查找节点,而 put 操作中,无论是更新现有节点还是添加新节点,都涉及到对双向链表的操作,这些操作的时间复杂度都是 O(1)。当缓存达到容量上限时,put 操作会移除最久未使用的节点,这也是 O(1) 时间复杂度的操作。

3. 小结一下

V哥的这个实现的关键在于维护一个双向链表,它可以帮助我们快速地访问、更新和删除最近最少使用的节点,同时使用哈希表来提供快速的查找能力。这样,我们就可以在 O(1) 的时间复杂度内完成所有的缓存操作。哈哈干净利索,回答完毕。

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

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

相关文章

LeetCode:2385. 感染二叉树需要的总时间(DFS Java)

目录 2385. 感染二叉树需要的总时间 题目描述&#xff1a; 实现代码与解析&#xff1a; DFS 原理思路&#xff1a; 2385. 感染二叉树需要的总时间 题目描述&#xff1a; 给你一棵二叉树的根节点 root &#xff0c;二叉树中节点的值 互不相同 。另给你一个整数 start 。在第…

Simulink从0搭建模型03-Enabled Subsystem 使能子系统

参考博客 b站视频 【Simulink 0基础入门教程 P4 使能子系统 Enabled Subsystem 的使用介绍】 个人听了这个博主的视频风格觉得很适合我入门学习&#xff0c;讲得很清楚。 另外&#xff0c;视频里面教得很详细了&#xff0c;我也不会再详细写怎么打开创建等步骤&#xff0c;跟着…

QT中对于QPushButton样式的调整

文章目录 前言1.QPushButton1.1 新建项目导入资源1.2 添加Push Button并定义样式1.3 调整样式1.4 实际需求情况1.5 背景色和边框 2. 一些概念理解2.1 图片2.2 边距 总结 前言 前段时间在调软件的样式&#xff0c;学到了些新的东西&#xff0c;也碰到了些问题&#xff0c;这里做…

电脑遗失d3dx9_43.dll文件会给电脑带来什么问题,有哪些方法可以解决丢失d3dx9_43.dll文件的办法

电脑遗失了d3dx9_43.dll文件&#xff0c;可能会引发一系列麻烦的后果。那么&#xff0c;针对这种情况&#xff0c;我们应该采取哪些方法来修复丢失的d3dx9_43.dll文件呢&#xff1f;下面将介绍几种解决d3dx9_43.dll文件丢失问题的有效方法。 对d3dx9_43.dll文件的简要介绍 d3d…

Flutter 中优雅切换应用主题的组件

Flutter 中优雅切换应用主题的组件 视频 https://youtu.be/L–XLpc452I https://www.bilibili.com/video/BV1wD421n75p/ 前言 原文 https://ducafecat.com/blog/flutter-app-theme-switch Adaptive Theme 这个组件通过包裹 MaterialApp 的方式整体管理 theme 主题&#xff0…

Linux中Ctrl+C和Ctrl+Z的区别_实战讲解(超详细)

1、CtrlC和CtrlZ的区别&#xff1a; CtrlZ&#xff1a;暂停进程 即挂起进程 暂停当前进程并将其放到后台。可以在后续恢复执行。 暂停的进程仍然占用系统内存 CtrlC&#xff1a;终止进程 终止进程后&#xff0c;程序代码占用的内存会释放掉 2、实战讲解 跑人工智能代码的时候…

Ts类型体操详讲 之 extends infer (下)

目录 1、函数 &#xff08;1&#xff09;提取参数类型 &#xff08;2&#xff09;提取返回值类型 2、构造器 &#xff08;1&#xff09;提取构造器返回值 &#xff08;2&#xff09;提取构造器参数类型 3、索引类型 本章我们继续上节的内容继续&#xff0c;展示我们对ex…

day12 过一遍Nestjs框架(java转ts全栈/3R教室)

介绍&#xff1a;NestJS是Ts技术栈的后端框架&#xff0c;相当于Java中的springboot。 学习方法&#xff1a;与java技术体系进行对比学习。学习目标&#xff1a;nest相关知识也是挺多&#xff0c;但对比学spring的时候&#xff0c;大部分在项目生产中都是套路化的&#xff0c;大…

Qt开发(二)打包发布

注意qt6生成的exe不能再win7&#xff08;包含win7&#xff09;以下运行 1、编译程序 编译程序不演示 2、找到exe文件 在这个路径下找到该exe文件 3、打包 新建一个文件夹 将exe放在该文件夹下除了exe开始这里面没有其他文件 找到安装目录下 在cmd中运行 把这个文件和编…

html2Canvas截图包含滚动条解决思路

概况描述 在项目中使用html2Canvas进行截图时发现无法截取滚动条部分&#xff0c;前端是使用vue2的版本&#xff0c;网上找了很多方式都没效果&#xff0c;冷静思考后&#xff0c;给出解决办法。 解决思路 当我们截取的div容器的宽和高与内部的子容器div的宽和高不一样时&am…

江开2024年春《大学英语(B)(2) 060052》过程性考核作业4参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 单选题 1阅读Passage One&#xff0c;回答C-1C-4个问题。请…

七星创客新零售系统:颠覆性商业模式的崛起

大家好&#xff0c;我是微三云周丽&#xff0c;今天给大家分析当下市场比较火爆的商业模式&#xff01; 小编今天跟大伙们分享什么是七星创客新零售系统&#xff1f; 随着经济的快速发展和科技的不断进步&#xff0c;商业模式的革新成为了企业发展的关键。在这个新旧动能转换、…

【2023】springboot通过阿里云oss进行文件单个批量文件上传下载

SpringBoot整合阿里OSS实现上传下载 目录&#x1f4bb; 前言一、介绍二、阿里云添加oss1、进入oss目录2、创建bucket3、测试上传下载4、创建AccessKey管理账号 三、依赖以及配置1、依赖2、yml3、Config类4、OSSUtil 工具类 四、controller五、测试1、测试上传2、测试删除 前言 …

牛客 题解

文章目录 day4_17**BC149** **简写单词**思路&#xff1a;模拟代码&#xff1a; dd爱框框思路&#xff1a;滑动窗口&#xff08;同向双指针&#xff09;代码&#xff1a; 除2&#xff01;思路&#xff1a;模拟贪心堆代码&#xff1a; day4_17 BC149 简写单词 https://www.now…

stm32F407-PS手柄代码,20240424

一、硬件接线 手柄接收器—stm32板子 GND—GND VCC—5V DAT–F3 CMD–F5 CS–F6 CLK–F4 二、PStwo.h #ifndef __PSTWO_H #define __PSTWO_H//F3 F5 F6 F4 F10 // #include "delay.h" #include "sys.h"#define DI PFin(3) //PB12 输入#defin…

C# Solidworks二次开发:枚举应用实战(第三讲)

大家好&#xff0c;今天继续介绍枚举相关内容。 下面是今天要介绍的枚举&#xff1a; &#xff08;1&#xff09;第一个为swACisOutputVersion&#xff0c;这个枚举为ACIS的版本&#xff0c;下面是官方的具体解释&#xff1a; 其枚举值为&#xff1a; MemberDescriptionswAc…

git切换源失败解决方案

git切换源失败解决方案 git切换源git切换源失败(无效) git切换源 git可以使用命令行切换源&#xff0c;一般使用的源有两个地址&#xff0c;git原生地址和淘宝镜像地址&#xff0c;部分公司会使用内部地址。 源切换后&#xff0c;npm i就是从源地址拉取相关依赖了。 原生地址…

选择合适的监控观测平台,为业务出海合规建设减负

在全球化商业版图的浩瀚海洋中&#xff0c;企业如同一艘勇敢的船只&#xff0c;突破重重阻碍&#xff0c;勇往直前。在这一征途中&#xff0c;监控观测活动是导航中的指南罗盘&#xff0c;确保企业航向正确、安全稳定&#xff0c;成为企业出海路上维护业务稳定和数据安全的关键…

【论文解析】笔触渲染生成 前沿工作梳理

最近的一些工作梳理 2023年 Stroke-based Neural Painting and Stylization with Dynamically Predicted Painting Region 2022年Im2Oil: Stroke-Based Oil Painting Rendering with Linearly Controllable Fineness Via Adaptive Sampling 文章目录 1 Stroke-based Neural P…

string 类以及模拟实现

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary_walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…