【面经 每日一题】面试题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实例时,内存泄漏会加速系统崩溃。)

相关推荐
时间的拾荒人14 分钟前
Redis 集合类型详解:Set 与 Zset
redis·缓存·面试
Pniubi21 分钟前
MySQL靠什么实现事务的?事务隔离级别有哪些?
java·数据库·mysql
wo_jack1 小时前
C++ 入门笔记(十七):list 入门 —— 为什么 list 不能 sort?
数据结构·c++·面试·stl·list
松仔log1 小时前
Kotlin中级——callbackFlow
java·前端·kotlin
知守观2 小时前
同一个团队两套分包:DDD 四层落地后,domain 层最大的类还是 5885 行
java·后端·领域驱动设计
Oneshot2 小时前
DLX(Dancing Links)算法详解:求解数独最快的算法
算法
文丑颜不良啊2 小时前
LeetCode2541~2560题目解析
java·leetcode
瑞雪兆丰年兮2 小时前
JavaWeb-day11-SpringBootWeb案例(文件上传)
java·运维·服务器·javaweb·oss
青少儿编程课堂3 小时前
蓝桥杯青少组图形化 + 全国青少年信息素养大赛 + 电子学会Scratch考级:校园连连看解析
c++·python·算法·bfs·信息学竞赛
kongdong3 小时前
Hikyuu | RSI均值回归交易策略实现
算法·均值算法·回归·量化交易·交易策略·hikyuu