leetCode 146. LRU缓存机制

题目

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

LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1void 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

思路:

  • 构建一个双向链表的哈希Map,也就是 LinkedHashMap, 带有前后节点
  • LRUCache 这个类,也需要初始化,在初始化中,构造伪头部节点和伪尾部节点
  • get操作,每次使用过后,要把对应的节点移到头部
    把对应的节点移到头部,包含了两步,
    一个是删除当前节点,一个是将节点添加到头部,分别设置各个链表节点的前后节点
  • put操作,如果key不存在,新建一个新的节点, 如果超出map的容量,删除双向链表的尾部节点。
    如果 key 存在,先通过哈希表定位,再修改 value,并移到头部。

易错点:

  • 存储双向链表的哈希 map, key 是哈希值,value 是链表节点。
    private Map<Integer, DoubleLinkedNode> cache = new HashMap<>();
  • 构造方法初始化,这里的LRU构造方法还需要一个容量的参数。
  • get 操作,key是int类型的, key不存在就返回-1

代码:

class LRUCache {

    /**
        内部类 LinkedHashMap。定义链表的节点
     */
    class DoubleLinkedNode {
        int key;
        int value;
        //上一个节点
        DoubleLinkedNode prev;
        //下一个节点
        DoubleLinkedNode next;

        public DoubleLinkedNode() {
        }

        /**
            构造方法
         */
        public DoubleLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }

    }

    //map的 key 是哈希值,value 是链表节点
    private Map<Integer, DoubleLinkedNode> cache = new HashMap<>();
    private int size;
    private int capacity;
    //首尾节点
    private DoubleLinkedNode head;
    private DoubleLinkedNode tail;

    /**
        注意:构造方法初始化,这里的LRU构造方法还需要一个容量的参数。
     */
    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        //使用伪头部和伪尾部节点
        head = new DoubleLinkedNode();
        tail = new DoubleLinkedNode();
        //双向链表
        head.next = tail;
        tail.prev = head;
    }
    
    public int get(int key) {
        DoubleLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        //如果key存在,先通过 哈希表定位,再移到头部
        moveToHead( node);
        return node.value;

    }
    
    public void put(int key, int value) {
        DoubleLinkedNode node = cache.get(key);
        if (node == null) {
            //如果key不存在,新建一个新的节点
            DoubleLinkedNode newNode = new DoubleLinkedNode(key, value);
            //添加进哈希表
            cache.put( key, newNode);
            //添加到双向链表的头部
            addToHead(newNode);
            size++;
            if (size > capacity) {
                //超出容量,删除双向链表的尾部节点
                DoubleLinkedNode tail = removeTail();
                //删除哈希表中对应的项
                cache.remove( tail.key);
                size--;
            }

        } else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node.value = value;
            moveToHead(node);
        }

    }

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


    /**
        将节点移动到头部
     */
    private void moveToHead(DoubleLinkedNode node) {
        //移除节点
        removeNode(node);
        //添加到头部
        addToHead(node);
    }

    /**
        移除节点
     */
    private void removeNode(DoubleLinkedNode node) {
        //注意:由于是双向链表,前后节点都得处理,这里不要漏了。
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    /**
        添加到头部
     */
    private void addToHead(DoubleLinkedNode node) {
        //node的前后节点
        node.prev = head;
        node.next = head.next;
        //node的前后节点,分别指向 node
        head.next.prev = node;
        head.next = node;

    }


}

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

posted on   乐之者v  阅读(6)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2024-01-24 kafka为什么不支持读写分离?
2022-01-24 SpringBoot2.X整合集成Dubbo
2022-01-24 IDEA启动项目报错:Caused by: java.io.FileNotFoundException: class path resource [.properties] cannot be opened because it does not exist
2018-01-24 java反射
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示