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);
 */

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

相关推荐
周三有雨4 分钟前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
爱米的前端小笔记26 分钟前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
好学近乎知o32 分钟前
解决sql字符串
面试
trueEve1 小时前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展
九圣残炎3 小时前
【从零开始的LeetCode-算法】3354. 使数组元素等于零
java·算法·leetcode
Dlwyz3 小时前
问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
数据库·redis·缓存
程序猿小柒3 小时前
leetcode hot100【LeetCode 4.寻找两个正序数组的中位数】java实现
java·算法·leetcode
_OLi_4 小时前
力扣 LeetCode 106. 从中序与后序遍历序列构造二叉树(Day9:二叉树)
数据结构·算法·leetcode
我明天再来学Web渗透5 小时前
【SQL50】day 2
开发语言·数据结构·leetcode·面试
吴半杯6 小时前
Redis-monitor安装与配置
数据库·redis·缓存