[面试手撕4][LRU]4.LC16.25 LRU缓存:手撕LinkedList+结合map

78 阅读2分钟
class LRUCache {
    // 双向链表节点类,包含 key、value 和双向指针
    class DLinkedNode {
        int key;            // 节点的key
        int value;          // 节点的value
        DLinkedNode prev;   // 双向链表的前驱指针
        DLinkedNode next;   // 双向链表的后继指针
​
        public DLinkedNode() {
        };
​
        public DLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
​
    // 缓存,使用Map来快速查找节点
    Map<Integer, DLinkedNode> cache;
    // 容量,表示最大缓存量
    int capacity;
    // 当前缓存的大小
    int size;
    // 虚拟头节点,简化双向链表的操作
    DLinkedNode head;
    // 虚拟尾节点,简化双向链表的操作
    DLinkedNode tail;
​
    // LRUCache的构造函数,初始化缓存大小和双向链表
    public LRUCache(int capacity) {
        cache = new HashMap<>(); // 初始化缓存,使用哈希表来存储键值对
        this.capacity = capacity; // 设置缓存的最大容量
        size = 0; // 当前缓存的大小
        // 初始化双向链表的虚拟头尾节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        // 虚拟头尾节点互相连接
        head.next = tail;
        tail.prev = head;
    }
​
    // 获取缓存中的某个元素,若元素存在则移动到头部并返回其值,若不存在则返回-1
    public int get(int key) {
        DLinkedNode node = cache.get(key); // 从缓存中获取节点
        if(node == null) {
            return -1; // 如果缓存中没有该节点,返回-1
        }
        // 将节点移动到链表的头部
        MoveToHead(node);
        return node.value; // 返回节点的值
    }
​
    // 向缓存中插入元素,若缓存已满,则淘汰最不常使用的元素
    public void put(int key, int value) {
        DLinkedNode node = cache.get(key); // 获取缓存中是否已有该节点
        if(node != null) { // 如果节点已存在
            MoveToHead(node); // 移动该节点到链表头部
            node.value = value; // 更新节点的值
        } else { // 如果节点不存在
            DLinkedNode newNode = new DLinkedNode(key, value); // 创建一个新的节点
            cache.put(key, newNode); // 将新节点加入缓存
            size++; // 增加缓存的大小
            addToHead(newNode); // 将新节点添加到链表头部
            if(size > capacity) { // 如果缓存大小超过容量
                // 删除链表尾部的节点(最不常使用的节点)
                DLinkedNode del = removeTail();
                cache.remove(del.key); // 从缓存中移除该节点
                size--; // 缩小缓存大小
            }
        }
    }
​
    // 1. 添加节点到链表的头部
    public void addToHead(DLinkedNode node) {
        // 设定节点的 next 和 prev 指针,插入到头部
        node.next = head.next; 
        node.prev = head;
        head.next.prev = node; // 让原本头部节点的 prev 指向新节点
        head.next = node; // 让头部节点的 next 指向新节点
    }
​
    // 2. 删除链表中的某个节点
    public void removeOne(DLinkedNode node) {
        // 修改前后节点的指针,移除当前节点
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
​
    // 3. 删除链表的尾部节点
    public DLinkedNode removeTail() {
        // 获取尾部节点(最不常用的节点)
        DLinkedNode res = tail.prev;
        removeOne(res); // 从链表中移除该节点
        return res; // 返回被移除的节点
    }
​
    // 4. 将节点移动到链表头部,表示该节点被访问过
    public void MoveToHead(DLinkedNode node) {
        removeOne(node); // 移除该节点
        addToHead(node); // 将该节点添加到链表头部
    }
}
​
/**
 * 使用该LRUCache类的方式示例:
 * LRUCache obj = new LRUCache(capacity); // 初始化缓存,指定容量
 * int param_1 = obj.get(key); // 获取指定key的值
 * obj.put(key,value); // 插入key和value到缓存中
 */