首页 > 编程语言 >【算法】【线性表】【链表】LRU 缓存2

【算法】【线性表】【链表】LRU 缓存2

时间:2024-08-08 09:06:41浏览次数:13  
标签:线性表 get int value 链表 LRUNode LRU key put

1  题目

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类:
  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多调用 2 * 105 次 get 和 put

2  解答

上次我写了一个,今儿再写一个比较清晰的,主要思路就是:get 的时候,如果存在的话,要把它移动到头部,put的时候如果 key 存在,更新value后要把它移动到头部,不存在的话,要看删不删旧的,并把新的添加到头部:

public class LRUCache {
    // 容量大小
    private int capacity;
    // 链表头节点 方便处理
    private LRUNode head;
    // 某个元素存在或者不存在 所以需要 map 进行判断
    private Map<Integer, LRUNode> map;
    public LRUCache(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException("容量不能小于0");
        this.capacity = capacity;
        this.head = new LRUNode(0, 0);
        this.map = new HashMap<>(capacity);
        this.head.preNode = this.head;
        this.head.nextNode = this.head;
    }
    // 链表节点
    class LRUNode {
        private int key;
        private int value;
        private LRUNode preNode;
        private LRUNode nextNode;

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

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

        public LRUNode(int key, int value, LRUNode preNode, LRUNode nextNode) {
            this.key = key;
            this.value = value;
            this.preNode = preNode;
            this.nextNode = nextNode;
        }
    }

    public int get(int key) {
        // 不存在直接返回 -1
        LRUNode node = map.get(key);
        if (Objects.isNull(node)) {
            return -1;
        }
        // 就一个元素的话或者它本身就在第一个 不需要移动
        int res = node.value;
        if (map.size() <= 1) return res;
        if (node.preNode == this.head) return res;
        // 说明大于1个元素并且它还不在第一的位置,就需要把它移动到头部去
        // 为什么需要移动?因为put满的情况下要删除最不常用的 所以要移动保证删除的时候是O(1)
        LRUNode preNode = node.preNode;
        // 因为有头节点的存在 所以这个前置节点一定不为空
        LRUNode nextNode = node.nextNode;
        // 将当前要移动的节点 先释放出来
        nextNode.preNode = preNode;
        preNode.nextNode = nextNode;
        // 该节点的前后都重新指向
        node.preNode = this.head;
        node.nextNode = head.nextNode;
        // 插进来
        head.nextNode.preNode = node;
        head.nextNode = node;
        // 返回结果
        return res;
    }

    public void put(int key, int value) {
        LRUNode node = map.get(key);
        // 存在的话,直接更新
        if (Objects.nonNull(node)) {
            node.value = value;
            // 这里很重要 因为存在更新完value 要把这个节点释放出来,下边要对这个节点移动到头部
            LRUNode preNode = node.preNode;
            LRUNode nextNode = node.nextNode;
            nextNode.preNode = preNode;
            preNode.nextNode = nextNode;
        } else {
            // 没有的话 说明不存在需要把它插进来
            // 先判断满没满
            if (map.size() < this.capacity) {
                // 没满直接头插法把它带进来
                node = new LRUNode(key, value);
                this.map.put(key, node);
            } else {
                // 满了的话 需要移出尾部
                node = this.head.preNode;
                map.remove(node.key);
                node.key = key;
                node.value = value;
                map.put(key, node);
                LRUNode lastPreNode = node.preNode;
                lastPreNode.nextNode = this.head;
                this.head.preNode = lastPreNode;
            }
        }

        // node即为需要插入到头部的节点
        // 重新赋值 node 信息
        node.nextNode = this.head.nextNode;
        node.preNode = this.head;
        // 放到头部
        LRUNode headNextNode = this.head.nextNode;
        headNextNode.preNode = node;
        this.head.nextNode = node;
    }

    // 打印链表信息
    @Override
    public String toString() {
        LRUNode node = this.head.nextNode;
        StringBuilder builder = new StringBuilder();
        while (node != this.head) {
            builder.append(node.key).append("(").append(node.value).append(")");
            node = node.nextNode;
        }
        return builder.toString();
    }
}

打败的人数有点少哇,哈哈哈,上次写的那个一会儿moveLast 一会儿 moveFirst 的不够精简,这次写的简单点,有理解不对的地方欢迎指正哈。

标签:线性表,get,int,value,链表,LRUNode,LRU,key,put
From: https://www.cnblogs.com/kukuxjx/p/18348227

相关文章

  • C语言菜鸟入门·数据结构·链表超详细解析
     目录1. 单链表1.1 什么是单链表1.1.1  不带头节点的单链表1.1.2 带头结点的单链表1.2 单链表的插入1.2.1 按位序插入(1)带头结点(2)不带头结点1.2.2 指定结点的后插操作1.2.3 指定结点的前插操作1.3 单链表的删除1.3.1 按位序删除1.3.2 指......
  • 【数据结构】LinkedList与链表
    目录链表1、链表的概念及结构 2、LinkedList的使用2、1什么是LinkedList2、2LinkedList的使用3、LinkedList的遍历4、LinkedList的模拟实现 5、ArrayList和LinkedList的区别上篇已经熟悉了ArrayList的使用,ArrayList底层使用数组来存储元素。由于其底层是一段连续......
  • 链表的使用和总结
    一:基本知识2:特点:内存不连续,通过指针链接解决:长度固定的问题,插入删除麻烦的问题逻辑结构:线性结构存储结构:链式存储操作:增删改查二:单向链表结构体:structnode_t{ intdata;//数据域 structnode_t*next;//指针域};2.1.1分类1>有头单向链表存在一个头节点,数据......
  • Leetcode 141. 环形链表(超详图解,解析过程)
    141.环形链表点击跳转leetcode原题给你一个链表的头节点head,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。注意:pos......
  • 每日一题:Leetcode-24 两两交换链表中的节点
    力扣题目解题思路java代码力扣题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。示例1:输入:head=[1,2,3,4]输出:[2,1,4,3]示例2:输入:head=[]输出:[]示例3:输入:head=[1]......
  • 203. 移除链表元素 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满
    在链表中,每个节点都有一个指向下一个节点的指针。删除一个节点的本质是将前一个节点的指针指向要删除节点的下一个节点,从而跳过要删除的节点。以下是详细解释为什么以及如何这样做:1.**链表的结构**:  一个链表节点包含两个部分:存储的数据和指向下一个节点的指针。  ``......
  • 数据结构——链表
    数据结构——链表概念代码实现节点申请节点空间尾插头插尾删头删查找在指定位置之前插入数据在指定位置之后插入数据删除pos节点删除pos后一节点销毁链表概念链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的......
  • 数据结构----链表
        接下来我打算更新数据结构相关方面的知识,使用的语言是Java,但是其中的逻辑和思路并不影响。如果感兴趣可以关注合集。    希望大家看完之后可以自己去手敲实现一遍,同时在最后我也列出一些基本和经典的题目,可以尝试做一下。大家也可以自己去力扣或者洛谷牛客......
  • 【链表OJ】常见面试题 2
    文章目录1.[链表分割](https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking)1.1题目要求1.2哨兵位法2.[链表的回文结构](https://www.nowcoder.co......
  • 【数据结构】反转链表,合并有序链表,有无环的判断
    前言:小编在上期进行了单链表的模拟,这期接上期进行单链表相关题目讲解1.反转单链表 1.1.题目题目来源:.-力扣(LeetCode)给定一个单链表,实现单链表的反转,图示如下:1.2.解题思路首先在反转时,应该用到头插法,即将第一个后面的元素逐步插入到头结点之前,这里头结点每次要进......