深度解析: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 小时前
Redis入门指南(七):从零到分布式缓存-主从复制与哨兵机制
redis·分布式·缓存·主从复制·哨兵
没有bug.的程序员2 小时前
Spring Boot 与 Redis:缓存穿透/击穿/雪崩的终极攻防实战指南
java·spring boot·redis·缓存·缓存穿透·缓存击穿·缓存雪崩
panzer_maus2 小时前
Redis介绍(10)-缓存
数据库·redis·缓存
老友@2 小时前
Redis 脑裂(Split-Brain)
数据库·redis·缓存·脑裂
踩坑记录11 小时前
leetcode hot100 2.两数相加 链表 medium
leetcode·链表
派大鑫wink15 小时前
【Day61】Redis 深入:吃透数据结构、持久化(RDB/AOF)与缓存策略
数据结构·redis·缓存
小龙报16 小时前
【C语言进阶数据结构与算法】单链表综合练习:1.删除链表中等于给定值 val 的所有节点 2.反转链表 3.链表中间节点
c语言·开发语言·数据结构·c++·算法·链表·visual studio
Jia ming18 小时前
TLB与高速缓存:加速地址与数据的双引擎
缓存·tlb
xuedingbue18 小时前
数据结构与顺序表:高效数据管理秘籍
数据结构·算法·链表