深度解析:146. LRU 缓存(哈希表 + 双向链表的艺术)

LRU (Least Recently Used) 的核心在于:"如果一个数据最近被访问过,那么它在未来被访问的概率也更高。"

为了达到 的操作效率,我们需要两种数据结构的结合:

  1. 哈希表 (Map) :提供 的随机访问能力,通过 key 快速定位节点。
  2. 双向链表 (Doubly Linked List):提供 的节点移动能力,用来维护访问顺序。

实现细节优化

1. 定义双向链表节点

使用双向链表是因为删除一个节点时,我们需要知道它的前驱节点。

javascript 复制代码
class ListNode {
    constructor(key, value) {
        this.key = key;
        this.value = value;
        this.prev = null;
        this.next = null;
    }
}
2. LRU 缓存类定义

技巧: 使用"哨兵节点"(Dummy Head/Tail)可以极大简化边界条件的处理,避免判断 null

javascript 复制代码
class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.map = new Map();
        
        // 初始化哨兵节点,它们不存储实际数据
        this.head = new ListNode(-1, -1);
        this.tail = new ListNode(-1, -1);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

关键内部方法(封装逻辑)

将复杂的链表操作封装为私有方法,使 getput 的主逻辑像英语一样易读。

javascript 复制代码
    // 将节点移至头部(表示最近使用)
    moveToHead(node) {
        this.removeNode(node);
        this.addToHead(node);
    }

    // 从链表中彻底断开节点
    removeNode(node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // 在哨兵头节点后插入新节点
    addToHead(node) {
        node.prev = this.head;
        node.next = this.head.next;
        this.head.next.prev = node;
        this.head.next = node;
    }

    // 弹出最久未使用的节点(尾部哨兵前的一个)
    removeTail() {
        const lastNode = this.tail.prev;
        this.removeNode(lastNode);
        return lastNode; // 返回节点以便从 map 中删除
    }

主接口实现

javascript 复制代码
    /** * @param {number} key
     * @return {number}
     */
    get(key) {
        if (!this.map.has(key)) return -1;
        
        const node = this.map.get(key);
        this.moveToHead(node); // 刷新位置
        return node.value;
    }

    /** * @param {number} key 
     * @param {number} value
     */
    put(key, value) {
        if (this.map.has(key)) {
            const node = this.map.get(key);
            node.value = value; // 更新值
            this.moveToHead(node);
        } else {
            const newNode = new ListNode(key, value);
            this.map.set(key, newNode);
            this.addToHead(newNode);
            
            if (this.map.size > this.capacity) {
                const removed = this.removeTail();
                this.map.delete(removed.key); // 必须在节点中存储 key 才能在此删除 map 记录
            }
        }
    }
}
相关推荐
计算机安禾1 天前
【C语言程序设计】第37篇:链表数据结构(一):单向链表的实现
c语言·开发语言·数据结构·c++·算法·链表·蓝桥杯
我真会写代码1 天前
从入门到精通:Kafka核心原理与实战避坑指南
分布式·缓存·kafka
liuyao_xianhui1 天前
优选算法_模拟_提莫攻击_C++
开发语言·c++·算法·动态规划·哈希算法·散列表
我真会写代码1 天前
Redis高频面试题(含标准答案,覆盖基础+进阶+实战)
数据库·redis·缓存
6+h1 天前
【Redis】缓存问题及解决方案
数据库·redis·缓存
6+h1 天前
【Redis】高可用核心讲解
数据库·redis·缓存
007张三丰1 天前
常用缓存技术全方位解析:从本地缓存到分布式缓存
分布式·缓存
Aloha_up1 天前
redis与数据库的一致性问题分析
数据库·redis·缓存
Book思议-1 天前
【数据结构实战】双向链表:在指定位置插入数据
c语言·数据结构·算法·链表
窝子面1 天前
LeetCode练题三:链表
算法·leetcode·链表