【链表】Leetcode 146. LRU 缓存【中等】

news/2024/7/7 21:35:32

LRU 缓存

请你设计并实现一个满足 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) 的平均时间复杂度运行。

示例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

解题思路

LRU Cache是一种常见的缓存替换策略,可以采用哈希表和双向链表结合的方式实现。 哈希表用于快速查找节点,双向链表用于记录访问顺序。

  • 1、使用HashMap来存储key和对应的节点。
  • 2、使用双向链表来存储缓存的数据,链表头部是最近使用的数据,链表尾部是最久未使用的数据。
  • 3、对于get操作,如果key存在于缓存中,将对应的节点移到链表头部,并返回value;否则返回-1。
  • 4、对于put操作,如果key已经存在于缓存中,更新对应的value,并将节点移到链表头部;如果key不存在于缓存中,插入新的节点到链表头部,判断是否超过容量,若超过容量,删除链表尾部的节点。
  • 5、删除节点时,需要同时在HashMap中删除对应的key。

java实现

public class LRUCache {

    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
    }

    private HashMap<Integer, DLinkedNode> cache;
    private int size;
    private int capacity;
    private DLinkedNode head, tail;

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

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 将访问的节点移动到链表头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            // 如果key不存在,则创建新节点
            DLinkedNode newNode = new DLinkedNode();
            newNode.key = key;
            newNode.value = value;
            // 将新节点加入到链表头部
            cache.put(key, newNode);
            addToHead(newNode);
            size++;
            // 如果容量超限,则删除链表尾部节点
            if (size > capacity) {
                DLinkedNode tail = removeTail();
                cache.remove(tail.key);
                size--;
            }
        } else {
            // 如果key存在,则更新值,并将节点移到链表头部
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DLinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

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

    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }

    public static void main(String[] args) {
        // 创建容量为 2 的LRU缓存
        LRUCache lruCache = new LRUCache(2);

        // 插入 (1, 1)
        lruCache.put(1, 1);
        // 插入 (2, 2)
        lruCache.put(2, 2);
        // 获取键 1 的值,返回 1
        System.out.println(lruCache.get(1));
        // 插入 (3, 3),因为缓存容量为 2,需要移除最久未使用的键 2
        lruCache.put(3, 3);
        // 获取键 2 的值,返回 -1,因为键 2 已经被移除
        System.out.println(lruCache.get(2));
        // 插入 (4, 4),因为缓存容量为 2,需要移除最久未使用的键 1
        lruCache.put(4, 4);
        // 获取键 1 的值,返回 -1,因为键 1 已经被移除
        System.out.println(lruCache.get(1));
        // 获取键 3 的值,返回 3
        System.out.println(lruCache.get(3));
        // 获取键 4 的值,返回 4
        System.out.println(lruCache.get(4));
    }
}

时间空间复杂度

  • 时间复杂度:get和put操作均为O(1)的时间复杂度。
  • 空间复杂度:O(capacity),存储哈希表和双向链表所需的空间

http://lihuaxi.xjx100.cn/news/2122479.html

相关文章

再次度过我的创作纪念日

机缘 写博客的机缘巧合已经在上一篇博客中写到了&#xff0c;至于收获和成就也不一一赘述了。想和大家聊的呢就这最近这一年左右的经历吧 日常 自从2022年开始&#xff0c;入职了一家大型的项目外派公司&#xff0c;名字就不说了。开始了我的保险公司系统的开发工作。工作地点…

AI论文速读 | 具有时间动态的路网语义增强表示学习

论文标题&#xff1a; Semantic-Enhanced Representation Learning for Road Networks with Temporal Dynamics 作者&#xff1a; Yile Chen&#xff08;陈亦乐&#xff09; ; Xiucheng Li&#xff08;李修成&#xff09;; Gao Cong&#xff08;丛高&#xff09; ; Zhifeng Ba…

数据库SQLSever——数据查询

一、无条件查询 查询表的所有信息 SELECT * FROM 表名 例&#xff1a;查询学生表 SELECT * FROM student087 二、根据列名查询 根据列名查询表信息 SELECT [列名],[列名],.... FROM 表名 例&#xff1a;查询学生表的学生学号和姓名 SELECT SNO&#xff0c;SNAME FROM STU…

水泵干转检测

水泵干转检测是指在水泵工作时&#xff0c;监测水泵的运行状态&#xff0c;以便及时发现水泵出现干转&#xff08;也称为空转&#xff09;的情况。水泵干转是指水泵在没有输送液体的情况下运行&#xff0c;这可能会导致水泵叶轮、机械密封等部件损坏&#xff0c;甚至引起火灾等…

Android开发简易登录界面

title: Android开发第四天 search: 2024-03-22 tags: Android开发 Android开发简易登录界面 文章目录 Android开发简易登录界面一、定义style样式二、完成 activity_main.xml 界面具体设计三、代码简述 背景 &#xff1a;在初学 android 开发的时候&#xff0c;为了尽量熟悉学…

【每日跟读】常用英语500句(1~100)

【每日跟读】常用英语500句 What’s up? 怎么啦&#xff1f; I see. 我明白 Shut up. 闭嘴 Not bad. 还不错 I’ll do my best. 我会尽全力 Take it easy. 放轻松 On my way. 马上来 Are you serious? 你是认真的吗&#xff1f; See you later. 待会见 Good job. 做…

机器学习 - 神经网络分类

什么叫做分类问题&#xff1f; A classification problem involves predicting whether something is one thing or another. Problem typeWhat is it?ExampleBinary classificationTarget can be one of two options, e.g. yes or noPredict whether or not someone has hea…

2024年阿里云服务器2核8G配置活动价格分享,最低仅需652.32元1年

阿里云服务器2核8G配置2024年活动价格是多少&#xff1f;具体配置还需要看想要购买的云服务器实例规格和配置及带宽大小&#xff0c;目前在阿里云2024年活动中&#xff0c;2核8G配置价格最低的是通用算力型u1实例&#xff0c;价格只要652.32元1年&#xff0c;除此之外&#xff…