题解 | 实现一个LRU 缓存

题目描述:

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。

它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

// 来源:力扣(LeetCode)
// 链接:https://leetcode-cn.com/problems/lru-cache-lcci
// 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解思路:

通过一个哈希表 + 一个双向链表实现,时间复杂度(O(1))。 双向链表的头部存放最近访问的节点,尾部存放最久未访问的节点

  • 对于get()操作:
    • 先判断哈希表中是否存在key。
    • 如果不存在则直接返回-1;
    • 如果存在,则通过hashmap定位到链表中key对应的节点,删除该节点,然后在链表头部插入一个相同的节点(通过删除+添加 替换移动操作,因为移动时间复杂度更高),返回key对应节点的value值。
  • 对于put()操作:
    • 先判断哈希表中是否存在key。
    • 如果不存在,则在链表头部插入新节点(同时往hashmap中添加key),再判断链表长度是否超过缓存容量上限,如果超了,则删掉链表的尾部节点(同时删除hashmap中对应的key),如果没超,不做任何处理;
    • 如果存在,则通过hashmap定位到链表中key对应的节点,更新该节点的value为put的新value值,删掉,然后在头部插入一个相同的节点。

核心实现代码:

class LRUCache {

    // 定义链表节点类
    class LinkedNode{
        int key;
        int value;
        LinkedNode pre;
        LinkedNode next;

        public LinkedNode(){}

        public LinkedNode(int key, int value){
            this.key = key;
            this.value = value;
        }
    }

    private Map<Integer, LinkedNode> cacheMap = new HashMap<>(); // 定义一个Hashmap
    private int size; // 链表长度
    private int capacity; // 缓存容量
    private LinkedNode head; // 链表头节点
    private LinkedNode tail; // 链表尾节点
    
    // LRUCache的构造函数
    public LRUCache(int capacity) {
        this.size = 0; // 链表初始长度为0
        this.capacity = capacity;
        head = new LinkedNode(); // 虚拟头节点
        tail = new LinkedNode(); // 虚拟尾节点
        head.next = tail;
        tail.pre = head;
    }
    
    // LRUCache缓存的get方法
    public int get(int key) {
        LinkedNode node = cacheMap.get(key);
        if(node == null){
            return -1;
        }
        moveNode(node);
        return node.value;
    }
    
    // LRUCache缓存的put方法
    public void put(int key, int value) {
        LinkedNode node = cacheMap.get(key);
        if(node == null){
            LinkedNode newNode = new LinkedNode(key, value); // 新添加的节点
            cacheMap.put(key, newNode); // 别忘记更新cachaMap
            addNodeToHead(newNode);
            size++; // 插入新节点,链表长度+1
            if(size > capacity){ // 如果链表长度超过缓存容量上限,则删除链表尾部的节点
                LinkedNode node2 = removeTailNode();
                cacheMap.remove(node2.key);
                size--;
            }
        }else{
            node.value = value; // 先更新cachaMap中对应节点的value值
            moveNode(node); // 移动节点
        }
    }

    // 移动节点到链表头部
    public void moveNode(LinkedNode node){
        removeNode(node); // 先删除节点
        addNodeToHead(node); // 再在链表头部添加相同内容的节点
    }

    // 删除链表中某个节点
    public void removeNode(LinkedNode node){
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    // 在链表头部添加一个节点
    public void addNodeToHead(LinkedNode node){
        node.pre = head;
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
    }

    // 删除链表尾部节点,并返回
    public LinkedNode removeTailNode(){
        LinkedNode tailNode = tail.pre;
        removeNode(tailNode);
        return tailNode;
    }
}
全部评论

相关推荐

2025-12-24 15:25
已编辑
门头沟学院 前端工程师
是腾讯的csig腾讯云,前天晚上九点突然打电话约面,激动的通宵学了一晚上,第二天状态很差改了今天(以后再也不通宵学习了)感觉自己浪费了面试官一个半小时单纯手写+场景,无八股无项目无算法,打击真的很大,全是在面试官提醒的情况下完成的,自己技术方面真的还是有待提高,实力匹配不上大厂和已经面试的两个公司完全不一样,很注重编码能力和解决问题的能力,然而我这两个方面都很薄弱,面试官人很好很耐心的等我写完题目,遇到瓶颈也会提醒我,写不出题也会很耐心的跟我讲解好感动,到最后面试结束还安慰我打算把下周最后一场面试面完之后就不面啦,如果能去实习还是很开心,但是最重要的还是好好努力提高技术以下是面经第一题//&nbsp;实现一个解析&nbsp;url&nbsp;参数的函数function&nbsp;parseUrl(urlStr)&nbsp;{//&nbsp;TODO}parseUrl('*********************************************');//&nbsp;返回&nbsp;{a:&nbsp;1,&nbsp;b:&nbsp;2,&nbsp;c:&nbsp;3}追问:在链接里见过什么部分?用&nbsp;hash&nbsp;路由的话放在哪第二题//&nbsp;考虑有一个异步任务要执行,返回&nbsp;Promise,这个任务可能会失败,请实现&nbsp;retry&nbsp;方法,返回新方法,可以在失败后自动重试指定的次数。/***&nbsp;异步任务重试*&nbsp;@param&nbsp;task&nbsp;要执行的异步任务*&nbsp;@param&nbsp;times&nbsp;需要重试的次数,默认为&nbsp;3&nbsp;次*/function&nbsp;retry(task,&nbsp;times&nbsp;=&nbsp;3)&nbsp;{//&nbsp;TODO:&nbsp;请实现}//&nbsp;---------------测试示例&nbsp;----------------//&nbsp;原方法const&nbsp;request&nbsp;=&nbsp;async&nbsp;(data)&nbsp;=&gt;&nbsp;{//&nbsp;模拟失败if&nbsp;(Math.random()&nbsp;&lt;&nbsp;0.7)&nbsp;{throw&nbsp;new&nbsp;Error('request&nbsp;failed');}const&nbsp;res&nbsp;=&nbsp;await&nbsp;fetch(&#39;https://jsonplaceholder.typicode.com/posts&#39;,&nbsp;{method:&nbsp;'POST',body:&nbsp;JSON.stringify(data),});return&nbsp;res.json();}//&nbsp;新的方法const&nbsp;requestWithRetry&nbsp;=&nbsp;retry(request);//&nbsp;使用async&nbsp;function&nbsp;run()&nbsp;{const&nbsp;res&nbsp;=&nbsp;await&nbsp;requestWithRetry({&nbsp;body:&nbsp;'content'&nbsp;});console.log(res);}run();第三题就是给&nbsp;retry&nbsp;函数添加类型注释,用到泛型第四题:在组件库中将&nbsp;Alert&nbsp;用&nbsp;api&nbsp;的形式实现(应该就是&nbsp;message&nbsp;这个组件)怎么渲染到一个浮层里而不是原地渲染出来
不知道怎么取名字_:技术这个东西,太杂了,而且要下功夫的
查看5道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务