Leetcode面试经典150题-146.LRU缓存

解法都在代码里,不懂就留言或者私信,这个题大概率不会让你直接写代码,而是说以下思路,如果写代码这个题写出来基本就过了

java 复制代码
class LRUCache {
    /**首先我们得有缓存,get和put都是O(1)时间复杂度,我们常用的数据结构里应该只有Hashmap */
    Map<Integer, DoubleLinkedListNode> cache;
    int capacity;
    /**我们自定义了一个双向链表用于淘汰的操作*/
    DoubleLinkedList dll;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>();
        this.dll = new DoubleLinkedList();
    }
    
    public int get(int key) {
        /**我们想想cache这个map为什么是Map<Integer, DoubleLinkedListNode>而不是Map<Integer, Integer> ,因为我们要操作链表*/
        if(!cache.containsKey(key)) {
            return -1;
        }
        DoubleLinkedListNode node = cache.get(key);
        dll.moveToTail(node);
        return node.val;
    }
    
    public void put(int key, int value) {
        /**往里放值的时候需要判断这个值在原来的缓存中存在不存在,如果存在只是一个缓存的更新操作,更新完之后把这个节点从链表中某个位置移动到尾部即可 */
        if(cache.containsKey(key)) {
            DoubleLinkedListNode node = cache.get(key);
            node.val = value;
            dll.moveToTail(node);
        } else {
            /**如果不存在的话需要判断缓存是不是已经满了,如果满了就涉及淘汰了,淘汰哪个?淘汰双向链表头部的,因为它是访问时间距离现在最远的*/
            if(cache.size() == capacity) {
                DoubleLinkedListNode head = dll.removeHead();
                cache.remove(head.key);
            }
            /**包装成节点 */
            DoubleLinkedListNode node = new DoubleLinkedListNode(key, value);
            /**放入map */
            cache.put(key, node);
            /**放入双向链表 */
            dll.addNode(node);
        }
    }

    /**定义双向链表节点和双向链表类*/
    static class DoubleLinkedListNode {
        /**属性有前驱和后继节点以及自己的值 */
        DoubleLinkedListNode pre;
        DoubleLinkedListNode next;
        int key;
        int val;
        public DoubleLinkedListNode(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    static class DoubleLinkedList {
        /**对于双向链表来说,其实和单向链表没啥不一样的,都是有头有尾
        中间是双向还是单向靠节点的属性实现就行了,这里我也不写set get了,我们是写算法不是真实的项目,没必要的*/
        DoubleLinkedListNode head;
        DoubleLinkedListNode tail;
        /**加入节点是最基础的操作,这里就是加入节点,至于满不满,放在LRUCache里判断*/
        public void addNode(DoubleLinkedListNode node) {
            if(head == null) {
                /**还没有加入过节点,那加入了之后它既是头又是尾 */
                head = tail = node;
            } else {
                /**当前节点放在最后作为尾巴 */
                tail.next = node;
                /**千万不要忘了pre指针 */
                node.pre = tail;
                tail = node;
               
            }
        }
        /**LRU缓存的特点是哪个值的访问时间离现在越近,就是最热的应该保存,哪个离现在最远,应该就是缓存满了的时候被淘汰的
        我们这里双向链表用在LRU缓存里,需要有几个方法1. moveToTail用于某个key被访问了之后把它挪到尾部
        2.removeHead用于缓存满了之后淘汰(head放的是访问时间离现在最远的)
        removeHead需要有返回值,因为hashMap要同步删除*/
        public DoubleLinkedListNode removeHead() {
            /**强壮性校验,其实用不到,习惯了*/
            if(head == null) {
                return null;
            }
            /**拿到老的头部用于返回 */
            DoubleLinkedListNode oldHead = head;
            /**它的下一个节点作为新的头 */
            DoubleLinkedListNode next = head.next;
            /**断开和下一个节点的连接 */
            head.next = null;
            /**指认下个节点位头 */
            head = next;
            /**只有一个节点的时候next是不存在的,所以这里要判空 */
            if(head != null) {
                head.pre = null;
            }
            
            return oldHead;
        }
        /**某个数据被访问了之后变成热点数据的操作 */
        public void moveToTail(DoubleLinkedListNode node) {
            if(node == tail) {
                /**本来就在尾部,就别移动了把 */
                return;
            }
            /**如果是头部的话则涉及换头的操作*/
            if(node == head) {
                DoubleLinkedListNode next = node.next;
                /**不管if还是else里都有断开和后面节点连接的操作,放在最后的统一处理里 */
                head = next;
                head.pre = null;
            } else {
                /**不在尾部必有前驱,不在头部必有后继,把前驱和后继连一起*/
                DoubleLinkedListNode pre = node.pre;
                DoubleLinkedListNode next = node.next;
                pre.next = next;
                next.pre = pre;
            }
            /**最后进行通用的放在尾部的操作,放在尾部就把next置为null,两个原因:1尾部本来就没有后继 2.与原来的next断开连接 */
            node.next = null;
            tail.next = node;
            /**一共要注意pre指针 */
            node.pre = tail;
            tail = node;            
        }
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

这个题我的目的就是写出来,不知道是不是最优解,欢迎指导

相关推荐
LCG元1 小时前
【面试问题】JIT 是什么?和 JVM 什么关系?
面试·职场和发展
岁月变迁呀2 小时前
Redis梳理
数据库·redis·缓存
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭3 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
南宫生3 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
Lenyiin4 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
Code apprenticeship4 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
GISer_Jing6 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245526 小时前
吉利前端、AI面试
前端·面试·职场和发展
fpcc6 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
Ewen Seong6 小时前
mysql系列5—Innodb的缓存
数据库·mysql·缓存