leetcode-146.LRU缓存(易理解)

为了实现一个满足 LRU(最近最少使用)缓存约束的数据结构,我们需要在 (O(1)) 时间复杂度内完成 getput 操作。这通常可以通过结合使用哈希表和双向链表来实现:

  • 哈希表:用于在 (O(1)) 时间复杂度内实现对缓存中元素的快速访问。
  • 双向链表:用于维护缓存中元素的顺序,以便在缓存容量超出限制时能够快速定位并移除最久未使用的元素。

以下是 LRUCache 类的实现:

java 复制代码
import java.util.HashMap;
import java.util.Map;

class LRUCache {

    private class Node {
        int key;
        int value;
        Node prev;
        Node next;

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

    private final int capacity;
    private final Map<Integer, Node> cache;
    private final Node head;
    private final Node tail;

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

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }

        // Move the accessed node to the head
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) {
            // Create a new node
            Node newNode = new Node(key, value);
            cache.put(key, newNode);
            addNode(newNode);

            if (cache.size() > capacity) {
                // Pop the tail
                Node tail = popTail();
                cache.remove(tail.key);
            }
        } else {
            // Update the value
            node.value = value;
            moveToHead(node);
        }
    }

    private void addNode(Node node) {
        node.prev = head;
        node.next = head.next;

        head.next.prev = node;
        head.next = node;
    }

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

        prev.next = next;
        next.prev = prev;
    }

    private void moveToHead(Node node) {
        removeNode(node);
        addNode(node);
    }

    private Node popTail() {
        Node res = tail.prev;
        removeNode(res);
        return res;
    }

    public static void main(String[] args) {
        LRUCache lruCache = new LRUCache(2);
        lruCache.put(1, 1);
        lruCache.put(2, 2);
        System.out.println(lruCache.get(1)); // 返回 1
        lruCache.put(3, 3); // 该操作会使得关键字 2 作废
        System.out.println(lruCache.get(2)); // 返回 -1 (未找到)
        lruCache.put(4, 4); // 该操作会使得关键字 1 作废
        System.out.println(lruCache.get(1)); // 返回 -1 (未找到)
        System.out.println(lruCache.get(3)); // 返回 3
        System.out.println(lruCache.get(4)); // 返回 4
    }
}

解释

  1. Node 类 :用于表示双向链表中的节点,包含 keyvalue,以及前驱和后继节点的引用。
  2. 构造函数:初始化缓存容量、哈希表、以及双向链表的头尾虚拟节点。
  3. get 方法:检查缓存中是否存在指定键,若存在则将该节点移动到链表头部(表示最近使用),并返回其值;否则返回 -1。
  4. put 方法:插入新键值对时,若键已存在则更新值并移动到链表头部;若键不存在则创建新节点并插入链表头部,若超出容量则移除链表尾部节点(最久未使用)。
  5. 辅助方法
    • addNode:在链表头部插入节点。
    • removeNode:从链表中移除节点。
    • moveToHead:将节点移动到链表头部。
    • popTail:移除并返回链表尾部节点。

这种设计确保了所有操作的平均时间复杂度为 (O(1))。

相关推荐
ShiXZ21340 分钟前
Redis 常用指令全集:redis-cli 实战速查手册
数据库·redis·缓存
IKUN家族1 小时前
Spring MVC(一)
java·spring·mvc
老马识途2.02 小时前
关于跨域问题的总结
java·前端
都叫我大帅哥3 小时前
Java日期时间三十年战争:从Date考古到LocalDateTime革命,以及数据库与前端的那点事儿
java
Muscleheng3 小时前
SpringBoot 集成 DeepSeek 实现 RAG 文档问答
java·spring boot·ai·springai
2501_936415694 小时前
可变参数&综合练习&斗地主游戏
java·windows·游戏
极客先躯5 小时前
高级java每日一道面试题-2026年05月03日-实战篇[Docker]-如何实现容器化环境的数据加密?
java·运维·docker·容器·金融·加解密·高级面试
xqqxqxxq5 小时前
Java Socket 多人聊天室(私聊+群聊)技术笔记(V4版本)
java·网络·笔记
wang09076 小时前
自己动手写一个spring之aop_1
java·后端·spring
国科安芯6 小时前
FreeRTOS RISC-V 浮点上下文切换移植:在 IAR 工程中完整保存 FPU 寄存器
java·开发语言·单片机·嵌入式硬件·算法·系统架构·risc-v