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

相关推荐
阿巴~阿巴~2 小时前
IPv4地址转换函数详解及C++容器安全删除操作指南
linux·服务器·c++·网络协议·算法·c++容器安全删除操作·ipv4地址转换函数
青云交2 小时前
Java 大视界 -- 基于 Java 的大数据联邦学习在跨行业数据协同创新中的实践突破
java·分布式计算·隐私保护·apache flink·大数据联邦学习·跨行业数据协同·安全通信
合作小小程序员小小店2 小时前
桌面开发,在线%考试管理%系统,基于eclipse,java,swing,mysql数据库。
java·数据库·mysql·eclipse·jdk
oioihoii2 小时前
C/C++混合项目中的头文件管理:.h与.hpp的分工与协作
java·c语言·c++
一瓢一瓢的饮 alanchan2 小时前
Flink原理与实战(java版)#第2章 Flink的入门(第二节Flink简介)
java·大数据·flink·kafka·实时计算·离线计算·流批一体化计算
vx_bscxy3222 小时前
告别毕设焦虑!Python 爬虫 + Java 系统 + 数据大屏,含详细开发文档 基于微信小程序的民宿预约系统22398 (上万套实战教程,赠送源码)
java·spring boot·mysql·微信小程序·课程设计
SKYDROID云卓小助手2 小时前
无人设备遥控器之差分信号抗干扰技术
网络·stm32·单片机·嵌入式硬件·算法
美狐美颜SDK开放平台2 小时前
什么是美颜sdk?美型功能开发与用户体验优化实战
人工智能·算法·ux·直播美颜sdk·第三方美颜sdk·视频美颜sdk
z_鑫2 小时前
Java线程池原理深度解析
java·开发语言·后端