【链表】-Lc146-实现LRU(双向循环链表)

写在前面

  最近想复习一下数据结构与算法相关的内容,找一些题来做一做。如有更好思路,欢迎指正。


目录

  • 写在前面
  • 一、场景描述
  • 二、具体步骤
    • 1.环境说明
    • 2.双向循环链表
    • 3.代码
  • 写在后面


一、场景描述

  运用你所掌握的数据结构,设计和实现一个 LRU (Least Recently Used, 最近最少使用) 缓存机制。

它应该支持以下操作,获取数据 get 和 写入数据 put 。
1、获取数据 get(key), 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
2、写入数据 put(key, value), 如果密钥不存在,则写入其数据值,
当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?

示例:
LRUCache cache = new LRUCache(2);// 缓存容量

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

二、具体步骤

1.环境说明

名称说明
IntelliJ IDEA2019.2

2.双向循环链表

以下简单画了个图,说下双向循环链表的基本结构。

在这里插入图片描述

3.代码

以下为Java版本实现:

/**
 * LRU
 * 双向循环链表实现
 *
 * @author qiuxianbao
 * @date 2024/02/14
 * @since ace_1.4.0_20240109
 */
public class Lc146_LRU {

    public static void main(String[] args) {
        LRUCache cache = new LRUCache(2);
        cache.put(1, 1);
        System.out.println(cache);  // LRUCache{capacity=2, cacheMap.size=1, cacheMap={1=ListNode{key=1, value=1}}, dummy=ListNode{key=null, value=null}}
        cache.put(2, 2);
        System.out.println(cache);  // LRUCache{capacity=2, cacheMap.size=2, cacheMap={1=ListNode{key=1, value=1}, 2=ListNode{key=2, value=2}}, dummy=ListNode{key=null, value=null}}

        System.out.println(cache.get(1));  // 返回 1
        cache.put(3, 3);    // 该操作会使得密钥 2 作废
        System.out.println(cache);  // LRUCache{capacity=2, cacheMap.size=2, cacheMap={1=ListNode{key=1, value=1}, 3=ListNode{key=3, value=3}}, dummy=ListNode{key=null, value=null}}

        System.out.println(cache.get(2)); // 返回 -1 (未找到)

        cache.put(4, 4);    // 该操作会使得密钥 1 作废
        System.out.println(cache);  // LRUCache{capacity=2, cacheMap.size=2, cacheMap={3=ListNode{key=3, value=3}, 4=ListNode{key=4, value=4}}, dummy=ListNode{key=null, value=null}}


        System.out.println(cache.get(1)); // 返回 -1 (未找到)
        System.out.println(cache.get(3)); // 返回  3
        System.out.println(cache.get(4)); // 返回  4
    }

    /**
     * 思路:
     * 有 key 有 value,那么这个结点的 data有 int key, int value
     * 同时要具备很容易找到 头部(每次添加或获取时需要移动的) 和 尾部(删除) 节点
     * 因此,选择双向循环链表
     *
     * 为了减少查找,可以借助于Map当做缓存(快速获取元素),用于表示实际容器大小
     * 还需要有一个LRU的大小 capacity,用于和实际容器大小 cacheMap.size 比较
     *
     * put操作
     *      key存在,则更新 value,放到头部
     *      否则,判断是否超过容量
     *            超过,先删除链表尾部元素,然后再新增,放到头部
     *            否则,新增,放到头部
     * get操作
     *      每次都更新当结点到链表头部
     *
     * 不管是put,还是get,都保证头部都是最新的节点
     *
     * 定义 capacity,用于初始化存储容量
     * 定义一个 map 用于记录元素,方便判断是否存在以及根据 key去获取节点
     * 定义一个 双向循环链表,方便获取链表头部/尾部元素,便于添加/删除
     *
     * 定义构造函数,用于初始化数据包括dummy节点
     */
    static class LRUCache {
        // LRU大小
        int capacity;
        // 缓存,方便获取元素, size 就是元素实际的大小
        Map<Integer, ListNode> cacheMap;
        // 双向循环链表
        // dummy.next就是 head 节点,dummy.prev就是 tail 节点
        private ListNode dummy;

        public LRUCache(int capacity) {
            this.capacity = capacity;
            this.cacheMap = new HashMap<>();
            // 空元素初始化
            this.dummy = new ListNode();
            this.dummy.prev = this.dummy;
            this.dummy.next = this.dummy;
        }

        /**
         * 放元素
         * 也移动到头部,空间不足时,从尾部删除(需要有一个删除操作)
         *
         * @param k
         * @param val
         */
        public void put(int k, int val) {
            ListNode existNode = cacheMap.get(k);
            if (existNode != null) {
                existNode.value = val;
                move2Head(existNode);
            } else {
                if (cacheMap.size() >= this.capacity) {
                    // 删除尾部结点
                    ListNode tail = dummy.prev;
                    delete(tail);
                    // 删除缓存
                    cacheMap.remove(tail.key);
                }

                // 构建新节点,添加到头部
                ListNode listNode = new ListNode(k, val);
                add2Head(listNode);
                cacheMap.put(k, listNode);
            }
        }


        /**
         * 获取元素
         * 时时移动到头部
         *
         * @param k
         * @return
         */
        public int get(int k) {
            int result = -1;
            ListNode listNode = cacheMap.get(k);
            if (listNode != null) {
                result = listNode.value;
                move2Head(listNode);
            }
            return result;
        }

        /**
         * 移动到头部
         * 先删除,再添加
         *
         * @param e
         */
        private void move2Head(ListNode e) {
            delete(e);
            add2Head(e);
        }

        /**
         * 添加到头部
         *
         * @param e
         */
        private void add2Head(ListNode e) {
            ListNode head = dummy.next;

            e.next = head;
            head.prev = e;

            e.prev = dummy;
            dummy.next = e;
        }

        /**
         * 删除节点
         *
         * @param e
         */
        private void delete(ListNode e) {
            ListNode p = e.prev;
            ListNode n = e.next;

            p.next = n;
            n.prev = p;

            e.prev = null;
            e.next = null;
        }

        @Override
        public String toString() {
            return "LRUCache{" +
                    "capacity=" + capacity +
                    ", cacheMap.size=" + cacheMap.size() +
                    ", cacheMap=" + cacheMap +
                    ", dummy=" + dummy +
                    '}';
        }

        /**
         * 双向循环链表结构
         */
        class ListNode {
            // 数据
            Integer key;
            Integer value;

            // 前驱指针
            ListNode prev;
            // 后继指针
            ListNode next;

            public ListNode() {
            }

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

            @Override
            public String toString() {
                return "ListNode{" +
                        "key=" + key +
                        ", value=" + value +
                        '}';
            }
        }

    }

}


写在后面

  如果本文内容对您有价值或者有启发的话,欢迎点赞、关注、评论和转发。您的反馈和陪伴将促进我们共同进步和成长。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Objects类、包装类

一、Objects类定义 Objects类是一个工具类&#xff0c;包含static使用程序方法&#xff0c;是一个最终类不能被继承&#xff0c;提供了很多操作对象的静态方法。 二、Objects类的常见方法 1. Objects.equals()先做非空判断&#xff0c;再比较两个对象&#xff1a; 如果是拿对…

开源免费的Linux服务器管理面板分享

开源免费的Linux服务器管理面板分享 一、1Panel1.1 1Panel 简介1.2 1Panel特点1.3 1Panel面板首页1.4 1Panel使用体验 二、webmin2.1 webmin简介2.2 webmin特点2.3 webmin首页2.4 webmin使用体验 三、Cockpit3.1 Cockpit简介3.2 Cockpit特点3.3 Cockpit首页3.4 Cockpit使用体验…

Zotero插件分享(第二弹)

今天紧接上一篇文章&#xff08;Zotero常用插件分享&#xff09;&#xff0c;继续分享关于Zotero常用插件的相关内容。&#xff08;排名不分先后&#xff09; 1.Translate for Zotero 英文文献阅读辅助工具&#xff0c;可以实现将pdf中选中的文字翻译为指定语言&#xff0c;并…

智胜未来,新时代IT技术人风口攻略-第二版(弃稿)

文章目录 抛砖引玉 鸿蒙生态小科普焦虑之下 理想要落到实处校园鼎力 鸿蒙发展不可挡培训入场 机构急于吃红利企业布局 鸿蒙应用规划动智胜未来 技术人风口来临 鸿蒙已经成为行业的焦点&#xff0c;未来的发展潜力无限。作为一名程序员兼UP主&#xff0c;我非常荣幸地接受了邀请…

[NSSCTF]-Web:[SWPUCTF 2021 新生赛]easyrce解析

先看网页 代码审计&#xff1a; error_reporting(0); &#xff1a;关闭报错&#xff0c;代码的错误将不会显示 highlight_file(__FILE__); &#xff1a;将当前文件的源代码显示出来 eval($_GET[url]); &#xff1a;将url的值作为php代码执行 解题&#xff1a; 题目既然允许…

【闲谈】开源软件的崛起与影响

随着信息技术的快速发展&#xff0c;开源软件已经成为软件开发的趋势&#xff0c;并产生了深远的影响。开源软件的低成本、可协作性和透明度等特点&#xff0c;使得越来越多的企业和个人选择使用开源软件&#xff0c;促进了软件行业的繁荣。然而&#xff0c;在使用开源软件的过…

idm下载路径在哪 idm下载保存路径怎么设置 IDM下载器 internetdownloadmanager官方版下载 网络加速器

春节&#xff08;Spring Festival&#xff09;&#xff0c;是中国最隆重最富有特色的传统节日之一。春节期间我们与一家人团聚在一起&#xff0c;其乐融融。2024年春晚已经接近尾声了&#xff0c;很多人已经踏上了返程的路上。在部分地区&#xff0c;如春晚直播过程中或者网络高…

Excel练习:折线图突出最大最小值

Excel练习&#xff1a;折线图突出最大最小值 ​​ 要点&#xff1a;NA值在折现图中不会被绘制&#xff0c;看似一条线&#xff0c;实际是三条线。换成0值和""都不行。 ‍ 查看所有已分享Excel文件-阿里云 ‍ 学习的这个视频&#xff1a;Excel折线图&#xff0c…

力扣精选算法100道——矩阵区域和 (前缀和专题)

目录 &#x1f388;了解题意 &#x1f388;算法原理 &#x1f388;实现代码 &#x1f388;了解题意 给定一个大小为 m x n 的矩阵 mat 和一个整数 k&#xff0c;你需要计算一个新的矩阵 answer&#xff0c;其中每个 answer[i][j] 表示矩阵 mat 中以坐标 (i, j) 为中心、边…

Virt a Mate(VAM)游戏折腾记录

如有更新见原文&#xff1a;https://blog.iyatt.com/?p13283 1 前言 如果在网上看到有些视频名字带有 VAM 的&#xff0c;可能就是玩这个游戏录屏的。这个游戏可以建模、操作模型动作、构建场景等等。之前大致知道有这么个东西&#xff0c;只是电脑配置太差了&#xff0c;新…

pythn-scipy 查漏补缺

1. 2. 3. 4. 5. 6. 7. 8. 9. 偏度 skewness&#xff0c;峰度 kurtosis

STM32 寄存器操作 systick 滴答定时器 与中断

一、什么是 SysTick SysTick—系统定时器是属于CM3内核中的一个外设&#xff0c;内嵌在NVIC中。系统定时器是一个24bit的向下递减的计数器&#xff0c; 计数器每计数一次的时间为1/SYSCLK&#xff0c;一般我们设置系统时钟SYSCLK等于72M。当重装载数值寄存器的值递减到0的时候…

云原生介绍与容器的基本概念

云原生介绍 1、云原生的定义 云原生为用户指定了一条低心智负担的、敏捷的、能够以可扩展、可复制的方式最大化地利用云的能力、发挥云的价值的最佳路径。 2、云原生思想两个理论 第一个理论基础是&#xff1a;不可变基础设施。 第二个理论基础是&#xff1a;云应用编排理…

【牛客面试必刷TOP101】Day20.BM18 二维数组中的查找和BM19 寻找峰值

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;牛客面试必刷TOP101 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&…

Java学习第十三节之三种初始化和内存分析

三种初始化 package array;public class ArrayDemo02 {public static void main(String[] args) {//静态初始化&#xff1b;创建赋值int[] a {1, 2, 3, 4, 5, 6, 7, 8};System.out.println(a[0]);for (int i 0; i <a.length; i) {System.out.println(a[i]);}//动态初始化…

Java实现中学生家校互联系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 课堂表现模块2.3 考试成绩模块2.4 家校留言模块2.5 校园通知模块 三、系统设计3.1 用例设计3.2 实体类设计3.2.1 课堂表现实体类设计3.2.2 考试成绩实体类设计3.2.3 家校留言实体类设计3.2.4 校园通知实…

Netty应用(十二) 之 Netty相关参数 Http协议 IO多路复用回顾

目录 28.netty的相关参数 29.HTTP1.0、HTTP1.1 和 HTTP2.0 的区别 30.如何理解IO多路复用&#xff1f; 28.netty的相关参数 1.netty的参数设置体系 客户端&#xff1a; bootstrap.option(); //在这里配置客户端一些配置信息 服务端&#xff1a; serverBootstrap.option(…

【ES6】Promise

Promise 回调地狱 const fs require(fs);fs.readFile(./a.txt, utf-8, (err, data) > {if(err) throw err;console.log(data);fs.readFile(./b.txt, utf-8, (err, data) > {if(err) throw err;console.log(data);fs.readFile(./c.txt, utf-8, (err, data) > {if(er…

二叉树-------前,中,后序遍历 + 前,中,后序查找+删除节点 (java详解)

目录 提要&#xff1a; 创建一个简单的二叉树&#xff1a; 二叉树的前中后序遍历&#xff1a; 二叉树的前序遍历&#xff1a; 二叉树的中序遍历&#xff1a; 二叉树的后续遍历&#xff1a; 小结&#xff1a; 二叉树的前中后续查找&#xff1a; 二叉树的前序查找&#…

双链表简介

一.双链表 这里简单的介绍一下双链表&#xff0c;双链表也是和单链表是一种类型的结构&#xff0c;但是也有些许不同&#xff0c;其中不同的地方在于&#xff0c;双链表多了一个可以存储上一个单元的地址&#xff0c;并且是循环的链表&#xff0c;而且还增加了一个哨兵位&…