目录

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

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

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
振鹏Dong3 小时前
超大规模数据场景(思路)——面试高频算法题目
算法·面试
uhakadotcom3 小时前
Python 与 ClickHouse Connect 集成:基础知识和实践
算法·面试·github
uhakadotcom3 小时前
Python 量化计算入门:基础库和实用案例
后端·算法·面试
uhakadotcom3 小时前
使用Python获取Google Trends数据:2025年详细指南
后端·面试·github
uhakadotcom3 小时前
使用 Python 与 Google Cloud Bigtable 进行交互
后端·面试·github
uhakadotcom3 小时前
使用 Python 与 BigQuery 进行交互:基础知识与实践
算法·面试
uhakadotcom3 小时前
使用 Hadoop MapReduce 和 Bigtable 进行单词统计
算法·面试·github
longlong int4 小时前
【每日算法】Day 16-1:跳表(Skip List)——Redis有序集合的核心实现原理(C++手写实现)
数据库·c++·redis·算法·缓存
独行soc4 小时前
2025年渗透测试面试题总结- 某四字大厂面试复盘扩展 一面(题目+回答)
java·数据库·python·安全·面试·职场和发展·汽车
记得早睡~5 小时前
leetcode122-买卖股票的最佳时机II
javascript·数据结构·算法·leetcode