首页 > 其他分享 >【Leetcode Top 100】146. LRU 缓存

【Leetcode Top 100】146. LRU 缓存

时间:2024-12-07 11:58:27浏览次数:8  
标签:146 node capacity Top value 链表 LRU key 节点

问题背景

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

  • LRUCache(int capacity) 以 正整数 作为容量 c a p a c i t y capacity capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 k e y key key 存在于缓存中,则返回关键字的值,否则返回 − 1 -1 −1。
  • void put(int key, int value) 如果关键字 k e y key key 已经存在,则变更其数据值 v a l u e value value;如果不存在,则向缓存中插入该组 k e y − v a l u e key - value key−value。如果插入操作导致关键字数量超过 c a p a c i t y capacity capacity,则应该 逐出 最久未使用的关键字。
    函数 getput 必须以 O ( 1 ) O(1) O(1) 的平均时间复杂度运行。

数据约束

  • 1 ≤ c a p a c i t y ≤ 3000 1 \le capacity \le 3000 1≤capacity≤3000
  • 0 ≤ k e y ≤ 10000 0 \le key \le 10000 0≤key≤10000
  • 0 ≤ v a l u e ≤ 1 0 5 0 \le value \le 10 ^ 5 0≤value≤105
  • 最多调用 2 ≤ 1 0 5 2 \le 10 ^ 5 2≤105 次 getput

解题过程

数据结构设计题,以积累记忆为主。
LRU 是一种典型的页面淘汰策略,显然增删改的操作频率是比较高的,这应该用链表处理。这样一来,加入新页面的操作可以看作在链表头部插入,淘汰旧页面的操作可以看作删除链表的尾节点。
为了方便找到链表的尾节点,可以选择实现一个头尾循环的双向链表结构,这样一来,统一的头节点 d u m m y dummy dummy 的前一个节点就是尾节点。注意区别于一般的循环链表,这里双向链表是强调头尾节点的,而一般的循环链表最大的特征就是无所谓头尾,给定任意一个节点即可遍历整个链表。
还有一个问题,如果某个数据项已经存在,要求更新它的值和频率,也就是要将它的值更改为给定的新值并把这个节点移动到链表头部。但是在链表中按值查找的时间复杂度是 O ( N ) O(N) O(N) 量级的,不符合题目要求。为此,需要再引入查询效率为 O ( 1 ) O(1) O(1) 的哈希表。
综合来看,所有操作的时间复杂度是 O ( 1 ) O(1) O(1),需要 O ( m i n ( p u t , c a p a c i t y ) ) O(min(put, capacity)) O(min(put,capacity)) 的额外空间(可能出现记录的页面数量少,没有达到过 c a p a c i t y capacity capacity 的情况)。

具体实现

class LRUCache {

    // 自定义双向链表
    private static class ListNode {
        int key, value;
        ListNode pre, next;

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

    private final int capacity;
    private final ListNode dummy = new ListNode(0, 0);
    private final Map<Integer, ListNode> map = new HashMap<>();

    // 初始状态下头节点的前后指针指向自己
    public LRUCache(int capacity) {
        this.capacity = capacity;
        dummy.pre = dummy;
        dummy.next = dummy;
    }

    // 自己实现的方法一:双向链表的头插法
    private void pushFront(ListNode node) {
        node.pre = dummy; // 新节点的前驱是头节点
        node.next = dummy.next; // 新节点的后继是之前头节点的后继
        node.pre.next = node; // 新节点的前驱(就是头节点)的后继是新节点
        node.next.pre = node; // 新节点的后继的前驱是新节点
    }

    // 自己实现的方法二:双向链表中删除某个节点
    private void remove(ListNode node) {
        node.pre.next = node.next; // 当前节点前驱的后继是当前节点的后继
        node.next.pre = node.pre; // 当前节点后继的前驱是当前节点的前驱
    }

    // 自己实现的方法三:从哈希表中获取某个键对应的链表节点
    private ListNode getNode(int key) {
        // 该节点不存在则返回空
        if(!map.containsKey(key)) {
            return null;
        }
        // 存在则取出该节点,先删除,再从链表头部插入
        ListNode node = map.get(key);
        remove(node);
        pushFront(node);
        return node;
    }
    
    public void put(int key, int value) {
        // 根据相应的键取数据项,若存在则更新它的值
        ListNode node = getNode(key);
        if(node != null) {
            node.value = value;
            return;
        }
        // 若相应的数据项不存在,那么新建节点,更新哈希表并从链表头部插入
        node = new ListNode(key, value);
        map.put(key, node);
        pushFront(node);
        // 维护的页面数量超过规定值,进行淘汰
        if(map.size() > capacity) {
            // 头节点的前驱就是要淘汰的尾节点
            ListNode leastUsed = dummy.pre;
            // 从哈希表和链表中移除
            map.remove(leastUsed.key);
            remove(leastUsed);
        }
    }
        
    public int get(int key) {
        ListNode node = getNode(key);
        return node != null ? node.value : -1;
    }
}

/**
 * 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);
 */

标签:146,node,capacity,Top,value,链表,LRU,key,节点
From: https://blog.csdn.net/2401_88859777/article/details/144307830

相关文章

  • 树莓派通过终端和mydesktop文件实现py文件开机自启动程序方法(包括图形化界面)
    先说问题,在网上找的许多开机自启动方法都无法很好地让我实现python文件开机自启动,要么是完全没有用要么是只能执行部分,对于我设计的tkinter界面是无法打开的。my.desktop文件无法打开图像化界面,.bashrc文件只能在界面出来前启动,估计开机后被吞了。我的方法是通过my.desktop文......
  • 从0开始机器学习--12.决策分析-运筹优化与数学建模(决策分析方法,评价模型-层次分析法AH
    写在前面这些内容准确来说严格意义上不属于机器学习,把这部分内容归在这篇专栏中,主要原因之一是:机器学习算是与评价模型有关,且机器学习可以解决数学建模的问题。(其实就是我不想让这篇文章没有专栏归属,就把它聚类到这里了,后续若有更新其他运筹或数模的文章会再单独分类的~)机器......
  • M芯片parallels desktop安装arm win10“管理员已阻止你运行此应用mmc.exe”解决方法
    具体解决方法:1、由于MMC.exe无法运行,可能会遇到在运行中输入“gpedit.msc”命令无法打开本地组策略编辑器,所以我们鼠标右键点击“开始菜单”,点击“windowspowershell(管理员)”,或者是开始→window系统→命令提示符→右击选择以管理员身份运行,在windowspowershell(管理员)或者CMD命......
  • 中国开源网络安全软件Top 10 (2024版)
    在信息化快速发展的今天,网络安全问题愈加突出。中国的开源社区在网络安全领域贡献了许多优秀的开源软件,它们在实际应用中表现出色,广受好评。本文将介绍2024年最受欢迎的十款中国开源网络安全软件,并附上GitHub地址,供参考。雷池WAF社区版雷池WAF社区版是一款由长亭科技开发的Web......
  • 【Leetcode Top 100】138. 随机链表的复制
    问题背景给你一个长度为nnn的链表,每个节点包含一个额外增加的随机指针ra......
  • 使用 TOPIAM 轻松搞定「KubeSphere」单点登录
    本文将介绍 TOPIAM 与 KubeSphere 集成步骤详细指南。应用简介KubeSphere是在Kubernetes之上构建的以应用为中心的多租户容器平台,提供全栈的IT自动化运维的能力,简化企业的DevOps工作流。KubeSphere提供了运维友好的向导式操作界面,帮助企业快速构建一个强大和功能......
  • 【初阶数据结构与算法】二叉树顺序结构---堆的应用之堆排、Top-K问题
    文章目录一、堆排引入之使用堆排序数组二、真正的堆排1.向上调整算法建堆2.向下调整算法建堆3.向上和向下调整算法建堆时间复杂度比较4.建堆后的排序4.堆排序和冒泡排序时间复杂度以及性能比较三、Top-K问题一、堆排引入之使用堆排序数组   在了解真正的堆排之......
  • SpringBoot集成RabbitMQ的四种交换机(Direct、Topic、Fanout、Headers)
    以下是四种RabbitMQ交换机类型(Direct、Topic、Fanout、Headers)的详细实例代码,展示如何分别实现并使用它们。1.DirectExchange(直连交换机)DirectExchange将消息根据路由键(RoutingKey)发送到指定的队列。配置代码@ConfigurationpublicclassDirectExchangeConfig{​......
  • 高等教育自学考试_中国税制试题(课程代码:00146)_历年自考试题
     2024年4月高等教育自学考试_中国税制试题(课程代码:00146)-自考题库2023年10月高等教育自学考试_中国税制试题(课程代码:00146)-自考题库2023年4月高等教育自学考试_中国税制试题(课程代码:00146)-自考题库2022年10月高等教育自学考试_中国税制试题(课程代码:00146)-自......
  • PbootCMS中istop标签不起作用,如何确保文章在列表中置顶?
     在PbootCMS中,istop标签用于标识文章是否置顶。如果发现设置istop后文章没有在列表中置顶,可能是由于前端模板调用或配置的问题。以下是详细的排查和解决方法:确认后台设置:确保在后台正确设置了文章的置顶状态。登录后台管理系统,进入“内容管理”->“文章管理”,选择文章并勾......