hot100 146.LRU缓存

思路:如下图所示。

1.疑问一:需要几个哨兵节点?

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

2.疑问二:为什么节点要把key也存下来?

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

3.复杂度分析:

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

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

附代码:

写法一:标准库(面试一般不让调用标准库,需手写链表)。

java 复制代码
class LRUCache {
    private final int capacity;
    private final Map<Integer,Integer> cache = new LinkedHashMap<>(); //内置LRU
    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    
    public int get(int key) {
        //先删除key,并取出value做判断
        Integer value = cache.remove(key);
        if(value != null){ //如果在cache中就重新插入到末尾
            cache.put(key,value);
            return value;
        }
        //key不在 cache 中
        return -1;
    }
    
    public void put(int key, int value) {
        if(cache.remove(key) != null){ //先删除key,如果key已经存在
            cache.put(key,value); //再重新插入到末尾
            return;
        }
        //key不在cache中,那么就把key插入cache,插入前判断cache是否满了
        if(cache.size() == capacity){ //cache满了
            Integer eldestKey = cache.keySet().iterator().next(); //链表头部是从未被访问或最久未被访问的key,尾部是最后put的元素,也就是最近被访问的key
            cache.remove(eldestKey); //移除最久未使用的key
        }
        cache.put(key,value); //把当前元素put进来
    }
}

写法二:手写双向链表。

java 复制代码
class LRUCache {

    //双向链表节点
    private static class Node{
        // 初始化新节点的key和value,不初始化prev和next指针,默认为null
        int key,value;
        Node prev,next;

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

    private final int capacity;
    private final Node sentinel = new Node(0,0); //哨兵节点,节点的key和value初始化为0
    private final Map<Integer,Node> keyToNode = new HashMap<>();

    public LRUCache(int capacity) {
        this.capacity = capacity;
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }
    
    public int get(int key) {
        Node node = getNode(key); //getNode会把对应的节点移到链表的头部
        return node != null ? node.value : -1;
    }
    
    public void put(int key, int value) {
        Node node = getNode(key); //getNode会把对应节点移到链表的头部
        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 = sentinel.prev;
            keyToNode.remove(backNode.key);
            remove(backNode); //去掉最后一本书
        } 
    }
    //获取key对应的节点,同时把该节点移到链表头部
    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 = sentinel;
        x.next = sentinel.next;
        x.prev.next = x;
        x.next.prev = x;
    }
}
相关推荐
五仁烧饼11 分钟前
Unity Addressables 资源依赖治理:规则约束、自动修复与全链路防护
java·unity·游戏引擎
spider_xcxc12 分钟前
Jenkins 遇见 Argo CD:构建可靠 GitOps 流水线的实战指南
java·开发语言·servlet
AI人工智能+电脑小能手17 分钟前
【大白话说Java面试题 第214题】【10_网络协议篇】第5题:说一下 TCP 协议的三次握手和四次挥手
java·网络协议·tcp·三次握手·四次挥手
我叫唧唧波18 分钟前
【Java】Java 基础系统学习笔记
java·笔记·学习
CryptoPP28 分钟前
韩国股市数据源对接实战:从KOSPI到KOSDAQ的实时行情接入方案
数据库·redis·缓存·金融·区块链
好好沉淀32 分钟前
Spring @Validated和Validation注解 校验机制完全指南
java·数据库·后端
唐青枫35 分钟前
Java RxJava 实战指南:从 Observable、Flowable 到线程切换和背压处理
java
凤山老林1 小时前
SpringBoot + Configuration2 实现配置的实时双向更新
java·spring boot·后端
2601_963869953 小时前
【计算机毕业设计】基于 Spring Boot+Vue的手工体验馆管理系统的设计与实现
java·spring boot·后端
曹牧3 小时前
Java:BeanListHandler
java·数据库·oracle