146. LRU 缓存

146. LRU 缓存

!abstract 一句话本质 LRU = 哈希表提供"按 key 找到节点"的 O(1),双向链表提供"移动节点 / 淘汰尾部"的 O(1) 。两者缺一不可:哈希表管"查",链表管"顺序"。核心数据结构是 Map<key, 链表节点引用> + 环形 dummy 双向链表。
!quote 链接

题目

实现一个容量有限的缓存,支持 get(key)put(key, value),两者都要 O(1) 。容量满时,淘汰"最久未使用"的项。get 也算一次使用,会刷新该项的"最近使用"地位。

思路(从暴力到最优)

为什么必须用"哈希表 + 双向链表"两个结构?

要拿下 O(1),拆成两个需求:

  • get(key) 要 O(1) 找到节点 → 哈希表
  • :每次访问要把节点"拎出来 → 移到头部",满了还要删尾部 → 需要能 O(1) 删除中间节点。
    • 数组不行:删中间元素要搬后面所有元素,O(n)。
    • 单向链表不行:删节点要找到它的前驱,单向只能从头遍历,O(n)。
    • 双向链表 :每个节点有 prevnext,删中间节点改两根指针即可,O(1)。

关键设计:哈希表里存什么?节点里为什么要存 key?

  • 哈希表 value 存链表节点的引用 (不是存 value)。这样 get(key) 拿到引用,立刻知道它在链表里的位置,才能 O(1) 移到头部。
  • 链表的节点要额外存 key 。因为淘汰时手里只有"最久未用的那个节点",要能从 map 里删掉对应记录,必须有 key 才能 map.delete(node.key)。否则 map 里留下"幽灵数据"。

环形 dummy 哨兵:免判空的技巧

用一个 dummy 节点,初始化时 dummy.prev = dummydummy.next = dummy 首尾接成环。真实节点都插在 dummy 后面。

  • dummy.next = 最近使用,dummy.prev = 最久未用。
  • 为什么不用判空 :链表为空时 dummy 自环、结构合法;只剩一个真实节点时 dummy.next = nodedummy.prev = node,删掉它后 node 的 prev/next 都指回 dummy,链表变空但环依然成立。所以 remove / pushFront 永远在环上改指针,边界的前驱后继永远是有效节点,永远不为 null

关键卡点

!warning 我当时卡在这

  • map 里到底存 value 还是存节点引用? 存 value 拿到的是数据不是位置,没法 O(1) 移到头部。必须 Map<key, ListNode>
  • 节点里为什么还要存 key? 淘汰时手里只有链表节点,没有 key 就删不掉 map 里那条记录。
  • 环形 dummy 的价值:让 remove / pushFront 免判空,边界永远落在 dummy 上,不用处理"链表为空"和"删最后一个节点"两个特例。
  • #getNode 有副作用:它不只查节点,还顺手把节点移到头部。考题里 get 本身就是一次访问,刷新位置是符合语义的,不是 bug。
  • 容量判断直接用 keyToNode.size > capacity,不用自己维护 this.size(省得在多处 ++/-- 漏一处)。

代码

javascript 复制代码
class Node {
    constructor(key = 0, value = 0) {
        this.key = key;
        this.value = value;
        this.prev = null;
        this.next = null;
    }
}

class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.dummy = new Node();          // 环形哨兵
        this.dummy.prev = this.dummy;     // 首尾相接成环,免判空
        this.dummy.next = this.dummy;
        this.keyToNode = new Map();       // key -> 节点引用
    }

    // 取节点 + 顺手移到头部;不存在返回 null
    #getNode(key) {
        if (!this.keyToNode.has(key)) return null;
        const node = this.keyToNode.get(key);
        this.#remove(node);
        this.#pushFront(node);
        return node;
    }

    // 把 node 从链表中拎出来(前后邻居互相跳过它)
    #remove(node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // 把 node 插到 dummy 后面(成为最近使用)
    #pushFront(node) {
        node.prev = this.dummy;
        node.next = this.dummy.next;
        node.prev.next = node;
        node.next.prev = node;
    }

    get(key) {
        const node = this.#getNode(key);
        return node ? node.value : -1;
    }

    put(key, value) {
        let node = this.#getNode(key);   // 命中:已移到头部,只改 value
        if (node) {
            node.value = value;
            return;
        }
        node = new Node(key, value);
        this.keyToNode.set(key, node);
        this.#pushFront(node);
        if (this.keyToNode.size > this.capacity) {
            const backNode = this.dummy.prev;   // 最久未用 = 尾部
            this.keyToNode.delete(backNode.key); // 靠节点里的 key 清理 map
            this.#remove(backNode);
        }
    }
}

复杂度

  • 时间:O(1) ------ getput 都是 Map 查询 O(1) + 链表改指针 O(1),满了再淘汰 O(1)。
  • 空间:O(capacity) ------ map 和链表各存 capacity 个节点。

一句话记忆点

!tip 下次看到类似题 "O(1) 查 + O(1) 移顺序/淘汰" → 哈希表(查) + 双向链表(移),map 存节点引用,节点存 key 反向清理,环形 dummy 免判空。 看到"缓存 + 淘汰策略"就想到 LRU 模板。

相似题 / 套用模板

    1. LFU 缓存(在这基础上加"每个频率一个 LRU 链表",更进阶)
  • 任何"既要哈希表又要维护访问顺序"的设计题,核心都是这个双向链表 + 哈希表联动的模板。
相关推荐
天天喝旺仔12 分钟前
Git 内部原理深度解析:从 blob/tree/commit 对象到 packfile 与垃圾回收
数据结构·数据库·git·算法·哈希
AIGCmagic社区14 分钟前
KITTI AbsRel从6.5压到5.4,Marigold V2用一张32GB卡把编辑DiT收成单步深度估计
人工智能·算法·aigc·ai多模态
青少儿编程课堂15 分钟前
多源最短路与最小环(Floyd 算法图论解析)
c++·python·算法·bfs·信息学竞赛
6Hzlia23 分钟前
【Classic 150 刷题计划】 LeetCode 228. 汇总区间 | C++ 锚点游标与断点检测法
c++·算法·leetcode
Edward The Bunny38 分钟前
Leetcode Hot 100
数据结构·算法
hans汉斯41 分钟前
数据挖掘|基于BP神经网络的少数民族村寨文化型旅游体验产品潜在游客挖掘
深度学习·神经网络·算法·yolo·软件工程·bp·汉斯出版社
v_348360876210 小时前
Android native端 堆栈打印
android·算法
residual_fan10 小时前
【学术论文】航空发动机故障诊断智能体:基于持续对比强化学习的动态优化方法
人工智能·算法·数据挖掘·数据分析
GreenTea11 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
深圳满意度咨询12 小时前
医院满意度数字化测评系统:从数据采集到智能决策的技术实践
算法