【面经 每日一题】面试题16.25.LRU缓存(medium)

每日一题

面试题16.25.LRU缓存

一张图秒懂 LRU!

问:需要几个哨兵节点?

答:一个就够了。一开始哨兵节点 dummy 的 prev 和 next 都指向 dummy。随着节点的插入,dummy 的 next 指向链表的第一个节点(最上面的书),prev 指向链表的最后一个节点(最下面的书)。

问:为什么节点要把 key 也存下来?

答:在删除链表末尾节点时,也要删除哈希表中的记录,这需要知道末尾节点的 key。

代码

java 复制代码
public class LRUCache {
    private static class Node {
        int key, value;
        Node prev, next;

        Node(int k, int v) {
            key = k;
            value = v;
        }
    }

    private final int capacity;
    private final Node dummy = new Node(0, 0); // 哨兵节点
    private final Map<Integer, Node> keyToNode = new HashMap<>();

    public LRUCache(int capacity) {
        this.capacity = capacity;
        dummy.prev = dummy;
        dummy.next = dummy;
    }

    public int get(int key) {
        Node node = getNode(key);
        return node != null ? node.value : -1;
    }

    public void put(int key, int value) {
        Node node = getNode(key);
        if (node != null) { // 有这本书
            node.value = value; // 更新 value
            return;
        }
        node = new Node(key, value); // 新书
        keyToNode.put(key, node);
        pushFront(node); // 放在最上面
        if (keyToNode.size() > capacity) { // 书太多了
            Node backNode = dummy.prev;
            keyToNode.remove(backNode.key);
            remove(backNode); // 去掉最后一本书
        }
    }

    private Node getNode(int key) {
        if (!keyToNode.containsKey(key)) { // 没有这本书
            return null;
        }
        Node node = keyToNode.get(key); // 有这本书
        remove(node); // 把这本书抽出来
        pushFront(node); // 放在最上面
        return node;
    }

    // 删除一个节点(抽出一本书)
    private void remove(Node x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
    }

    // 在链表头添加一个节点(把一本书放在最上面)
    private void pushFront(Node x) {
        x.prev = dummy;
        x.next = dummy.next;
        x.prev.next = x;
        x.next.prev = x;
    }
}

复杂度分析

时间复杂度:所有操作均为 O(1)。

空间复杂度:O(min(p,capacity)),其中 p 为 put 的调用次数。

注意:

内部类(如Node)声明为static

在Java中,内部类(如Node)若声明为static,则不持有外部类(LRUCache)的引用。如果Node是非static(如原代码中的public class Node),则每个Node对象会隐式关联到一个LRUCache实例,导致:

内存泄漏风险:当LRUCache实例被销毁后,Node对象因持有外部类引用而无法被垃圾回收。

不必要的依赖:Node本身仅需存储键值和链表指针,无需访问LRUCache的实例字段(如capacity或dummy)。

思考引导:

如果Node是非static,当调用LRUCache cache = new LRUCache(10);后,cache被设为null,但Node对象仍因持有cache引用而无法回收。这会导致什么后果?

如果Node是非static(即默认内部类),当LRUCache实例被销毁后,Node对象仍隐式持有该实例的引用,导致垃圾回收器无法回收Node对象。后果是:内存持续泄漏,应用占用内存不断增长,最终可能引发OutOfMemoryError(内存溢出)。

(例如:频繁创建/销毁LRUCache实例时,内存泄漏会加速系统崩溃。)

相关推荐
爱读书的小胖7 分钟前
LeetCode 122:买卖股票的最佳时机 II(C)解题分析
算法
结局无敌7 分钟前
React Native for OpenHarmony 实战:三方库 react-native-modal-dialog 的鸿蒙化适配指南
深度学习·算法·react native·react.js·harmonyos
WJ.Polar10 分钟前
Java封装、继承、多态
java·开发语言
ly768921 分钟前
PostgreSQL 备份恢复策略:pg_basebackup、WAL 归档与 PITR 在企业 Java 系统中的落地
java·数据库·postgresql·pitr·pg_basebackup·wal 归档
摇滚侠22 分钟前
Java 中如何表示负数,有符号和无符号的区别,补码的计算规则,char byte short int long 的范围
java·开发语言
OPEN-F37 分钟前
ROS2系列教程:上位机建图与导航(原理综合)
算法·机器人
weixin_3077791338 分钟前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
桃桃乌龙_1 小时前
LangGraph 深度解析:从链到图,把 Agent 的循环画出来
面试
奇牙coding1 小时前
Codex CLI 升级后多文件编辑静默失效:config.yaml 枚举变更排查与修复指南
java·前端·javascript·ai
tomwang03221 小时前
路径优化建模、优化算法分类及工程化小结
算法·数学建模·分类