Leetcode(146)——LRU 缓存

LeetCode第146题解析 运用你所掌握的数据结构,设计和实现一个LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 进阶: 你是否可... 阅读详情

Leetcode(146)——LRU 缓存

题目

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1)O(1)O(1) 的平均时间复杂度运行。

示例:

输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4

提示:

  • 111 <= capacity <= 300030003000
  • 000 <= key <= 100001000010000
  • 000 <= value <= 10510^5105
  • 最多调用 2∗1052 * 10^52∗105 次 get 和 put

题解

方法一:哈希表+双向链表

思路

​​  LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。

  • 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
  • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

这样以来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1)O(1)O(1) 的时间内完成 get 或者 put 操作。具体的方法如下:

  • 对于 get 操作,首先判断 key 是否存在:
    • 如果 key 不存在,则返回 −1-1−1;
    • 如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
  • 对于 put 操作,首先判断 key 是否存在:
    • 如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
    • 如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。

上述各项操作中,访问哈希表的时间复杂度为 O(1)O(1)O(1),在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)O(1)O(1)。而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1)O(1)O(1) 时间内完成。

数据结构的使用原因

  • 为什么不用单链表?因为如果在 get 操作访问的是一个处于链表中间位置的节点时,使用双向链表可以快速获取其前一个节点和后一个节点的地址,并使其相连。
  • 为什么双向链表还要存储 key?因为在插入新值时,如果链表长度等于 capacity ,则要删除哈希表中的键值对,此时就需要获取最长未使用的键值来调用 erase 函数。
  • 哈希表的结构为 unordered_map<int, ListNode*>,以作到快速查找到对应的 key-value ,同时因为存储的是 ListNode*,所以可以重新对链表进行更新。

小贴士

在双向链表的实现中,使用一个 伪头部(dummy head) 和 伪尾部(dummy tail) 标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。

在这里插入图片描述

代码实现

我自己的:

class LRUCache {
    // 双向链表存储使用情况
    typedef struct dataUse{
        // 双向链表存一组 key-value,因为在超出capacity要删除hashmap里key-value pair 时。
        // 必须要通过Node里的key来删除,hashmap无法通过value来删除
        int key;
        int value;
        // 当get操作时候需要访问使用情况在中间的元素时,需要其前一个和后一个元素以方便重新连接
        struct dataUse *next;
        struct dataUse *last;
        dataUse():key(-1), value(-1), next(nullptr), last(nullptr){}
        dataUse(int k, int v, struct dataUse *nextone, struct dataUse *lastone):
            key(k), value(v), next(nextone), last(lastone){}
    }DU;
    // 哈希表保存其对应在链表中的地址
    unordered_map<int, DU*> cache;
    DU *h, *t;  // 头结点和尾结点,其中 h->next 则表示最晚未使用的关键字
    int maxsize;

    void movetoT(DU *p){
        p->last->next = p->next;
        p->next->last = p->last;
        p->last = t->last;
        p->next = t;
        t->last->next = p;
        t->last = p;
    }

    void addtoT(int key, int value){
        t->last->next = new DU(key, value, t, t->last);
        t->last = t->last->next;
    }

public:
    LRUCache(int capacity):h(new DU), t(new DU), maxsize(capacity){
        h->next = t;
        t->last = h;
    }
    
    int get(int key) {
        if(cache.count(key) == 0) return -1;
        else{
            if(t->last != cache[key]) movetoT(cache[key]);
            return cache[key]->value;
        }
    }
    
    void put(int key, int value) {
        if(cache.count(key) == 0){
            if(cache.size() == maxsize){
                cache.erase(h->next->key);    // 删除掉哈希表中太久未使用的关键对
                cache.emplace(key, h->next);
                cache[key]->key = key;
                cache[key]->value = value;
                movetoT(cache[key]);
            }else{
                addtoT(key, value);
                cache.emplace(key, t->last);
            }
        }else{
            if(t->last != cache[key]) movetoT(cache[key]);
            cache[key]->value = value;
        }
    }
};

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

Leetcode 官方题解:

struct DLinkedNode {
    int key, value;
    DLinkedNode* prev;
    DLinkedNode* next;
    DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
    DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

class LRUCache {
private:
    unordered_map<int, DLinkedNode*> cache;
    DLinkedNode* head;
    DLinkedNode* tail;
    int size;
    int capacity;

public:
    LRUCache(int _capacity): capacity(_capacity), size(0) {
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;
        tail->prev = head;
    }
    
    int get(int key) {
        if (!cache.count(key)) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        DLinkedNode* node = cache[key];
        moveToHead(node);
        return node->value;
    }
    
    void put(int key, int value) {
        if (!cache.count(key)) {
            // 如果 key 不存在,创建一个新的节点
            DLinkedNode* node = new DLinkedNode(key, value);
            // 添加进哈希表
            cache[key] = node;
            // 添加至双向链表的头部
            addToHead(node);
            ++size;
            if (size > capacity) {
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode* removed = removeTail();
                // 删除哈希表中对应的项
                cache.erase(removed->key);
                // 防止内存泄漏
                delete removed;
                --size;
            }
        }
        else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            DLinkedNode* node = cache[key];
            node->value = value;
            moveToHead(node);
        }
    }

    void addToHead(DLinkedNode* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    
    void removeNode(DLinkedNode* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }

    void moveToHead(DLinkedNode* node) {
        removeNode(node);
        addToHead(node);
    }

    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;
        removeNode(node);
        return node;
    }
};

网友题解:

struct lrunode{
    int val;
    int key;
    lrunode* next;
    lrunode* last;
};

class LRUCache {
public:
    lrunode* mp[10001];
    lrunode *list,*tail;
    int capacity;
    int size;
    LRUCache(int capacity) {
        for(int i=0;i<10001;++i)mp[i] = nullptr;
        this->capacity = capacity;
        list = tail = nullptr;
        size = 0;
    }
    
    int get(int key) {
        if(mp[key]!=nullptr){
            lrunode* t = mp[key];
            used(t);
            return t->val;
        }
        else return -1;
    }
    
    void put(int key, int value) {
        if(mp[key]!=nullptr){
            lrunode* t = mp[key];
            t->val = value;
            used(t);
        }else{
            lrunode* node = new lrunode;
            node->key = key;
            node->val = value;
            mp[key] = node;
            push_front(node);
            if(size<capacity){
                size++;
            }else{
                pop_back();
            }
        }
    }

    void used(lrunode* t){
        if(t==list)return;
        if(t->last)t->last->next = t->next;
        if(t->next)t->next->last = t->last;
        else tail = t->last;
        t->last = nullptr;
        t->next = list;
        if(list)list->last = t;
        list = t;
        if(tail == nullptr)tail = t;
    }

    void push_front(lrunode* node){
        node->last = nullptr;
        node->next = list;
        if(list)list->last = node;
        list = node;
        if(tail == nullptr)tail = node;
    }

    void pop_back(){
        lrunode* t = tail;
        if(list == tail){
            list = tail = nullptr;
        }else{
            tail = tail->last;
            tail->next = nullptr;
        }
        mp[t->key] = nullptr;
        delete t;
    }
};
复杂度分析

时间复杂度:对于 put 和 get 都是 O(1)O(1)O(1)。
空间复杂度:O(capacity)O(capacity)O(capacity),因为哈希表和双向链表最多存储 2∗capacity+22*\text{capacity} + 22∗capacity+2 个元素。

LeetCode 146. LRU 缓存(必看经典题) 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 阅读详情

相关推荐

【算法题解】27. 实现一个LRU缓存

设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

i余数的博客 774

Leetcode.146.LRU缓存机制

设计题:LRU:双向链表+HashMap

qq_43164662的博客 1312

LeetCode146——LRU缓存机制

我的LeetCode代码仓:https://github.com/617076674/LeetCode 原题链接:https://leetcode-cn.com/problems/lru-cache/description/ 题目描述: 知识点:哈希表、双向链表、LRU缓存机制 思路一:用哈希表记录键值对,用双向链表处理LRU缓存机制 用示例来说明我们算法的流程,为了防止对null节...

清风阁 1万+

[LeetCode解题] -- LRU缓存机制

146. LRU缓存机制 LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used。 [ 题目描述 ] 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字..

zhanggf的博客 731

leetcode 146 LRU 缓存

为了解决移到末尾这个问题,需要使用双向链表来记录,结构大概如下: https://leetcode.cn/problems/lru-cache/solutions/7583/shu-ju-jie-gou-fen-xi-python-ha-xi-shuang-xiang-li/对于链表,遇到上面的情况,也可以在常数的时间内找到对应的节点,但是如果想将它移到队尾则需要从头遍历到该节点才能保证链表不断,对于这种情况需要的时间复杂度也是O(n)// 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}

qq_24030999的博客 1248

【LeetCode 热题100】 146. LRU 缓存的算法思路及python代码

请你设计并实现一个满足缓存 约束的数据结构。实现LRUCachecapacitykeykeyvaluekey-valuecapacity函数get和put必须以O1的平均时间复杂度运行。

pljnb的博客 1329

146. LRU 缓存【 力扣(LeetCode) 】

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

yyh520025的博客 2779

【中等】力扣算法题解析LeetCode146:LRU缓存

本文介绍了一种基于哈希表和双向链表实现的LRU(最近最少使用)缓存机制,能够在O(1)时间复杂度内完成get和put操作。该设计通过维护访问顺序(最近访问节点靠近头部)和哈希快速查找,实现了高效缓存管理。具体实现中,双向链表用于记录访问顺序,哈希表用于快速定位节点。当缓存满时,会自动淘汰链表尾部的节点。文章提供了Java代码实现和详细注释,并解释了核心操作逻辑和辅助方法的功能。

ylumwd的博客 683

力扣146:LRU缓存

LRU缓存(Least Recently Used Cache)是一种常见的缓存淘汰策略,用于在缓存空间不足时决定移除哪些数据。优先淘汰最近最少使用的数据,保留最近频繁访问的数据。举个例子:安卓手机都可以把软件放到后台运行,比如我先后打开了「设置」「手机管家」「日历」,那么现在他们在后台排列的顺序是这样的:但是这时候如果我访问了一下「设置」界面,那么「设置」就会被提前到第一个,变成这样:假设我的手机只允许我同时开 3 个应用程序,现在已经满了。

yk_18的博客 753

LeetCode 146 - LRU缓存机制

的结合使用,以及对缓存淘汰策略(Least Recently Used)的实现。该题目在工程实践中十分常见,解法及其变体对面试和实际生产开发都具有重要意义。通过大量练习,熟练掌握 LRU 的实现以及链表、哈希表的结合模式,能够快速解决缓存机制相关问题!以下详细讲解不同解法、模板代码以及相关变体问题,帮助快速掌握并解决类似系统设计类问题。是一道经典的数据结构设计问题,考察对。与 LRU 反向,淘汰最近使用的缓存。利用 Java 提供的。

互联网架构师笔记 1151

【R语言】富集分析可视化代码(整理版)

分别介绍了如何使用DAVID在线分析工具对基因进行GO/KEGG功能富集分析、使用R ggplot包对获得的基因GO/KEGG功能富集结果进行可视化、使用R clusterProfiler包和R AnnotationHub包对基因进行GO/KEGG功能富集分析、OrgDb包制作以及结果可视化等。故,我重新整理了一波,并补充了可对多个分组的富集结果进行可视化以及富集结果绘制圈图等的代码。关注“在打豆豆的小潘学长”公众号,发送“富集分析3”获得完整代码包和演示数据。一、单组富集结果可视化。

weixin_54004950的博客 1万+

【数据结构】LRU缓存的简单模拟实现(leetcode力扣146LRU缓存)

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。

m0_74774759的博客 2314

kali设置安装burpsuite中文版

工具:通过网盘分享的文件:Burpsuite 链接:提取码: jay1。

m0_74786138的博客 5202

146. LRU 缓存 - 力扣(LeetCode)

请你设计并实现一个满足约束的数据结构。实现LRUCachecapacitykey-1keyvaluekey-valuecapacity函数get和put必须以O(1)的平均时间复杂度运行。输入输出解释// 缓存是 {1=1}// 缓存是 {1=1, 2=2}// 返回 1// 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}// 返回 -1 (未找到)// 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}// 返回 -1 (未找到)// 返回 3。

Dubhehug的博客 792

基于STM32F103C8T6的红外循迹避障小车

编程思路:控制核心板IO口的电平(即电机驱动模块的AIN1,AIN2,BIN1,BIN2)从而控制AO1、AO2、BO1、BO2的电平高低,进而控制电机使其正转、反转、不转。由于单纯地拉高拉低电平只能让电机在转与不转的状态切换,故决定利用TIM4的两个通道分别产生两路PWM信号来控制电机速度,四个电机两个一组,同一侧的两个连接在同一个PWM上。(#include "dianji.h"文件中同时写了对循迹模块引脚的初始化)

weixin_48743503的博客 1万+

使用Avalonia跨Linux平台

Avalonia类似使用WPF的技术方法来开发,所以,需要会使用WPF,其中的一些语法和写法与WPF有所区别,但是他们相差不大,会WPF的话,基本上可以看看文档后,完全掌握Avalonia的使用,从而达到跨平台开发的任务。其实写法有些不一样,可以参考官网的说明,进行修改。2. 安装完成后,重启VS2022,我们可以看到1和2,其中1是使用MVVM模式开发,2是普通模式开发,我们选择2,建立项目。9.直接双击粉红色的文件,进行安装,输入密码,但是美中不足的是,好像没有图标,图标似乎需要单独去设置。

故里2130 1万+

力扣——146.LRU缓存

注意也要把map里面的key删除,这个key在node里面有(这就是为什么node里面也要存key)。对于获取:先看一下map里面有没有这个key,有的话返回这个node,并把这个。

weixin_49332521的博客 1698

手把手教你用RTL8306MB搭建小型网络交换机:从原理到实战

本文详细介绍了如何使用RTL8306MB交换芯片从零搭建小型网络交换机的全过程。内容涵盖芯片原理分析、核心硬件电路设计(包括电源、时钟、RMII接口连接要点)、驱动开发与SMI通信实现,以及从物理链路到交换功能的系统调试实战。重点解析了将MAC5配置为RMII PHY模式与CPU对接的关键步骤和常见错误,帮助嵌入式开发者深入理解网络交换原理并完成实践。

weixin_29274969的博客 191

力扣LeetCode第146题LRU缓存

力扣LeetCode第146题LRU缓存

weixin_44061444的博客 1003

不藏了!Wireshark 命令、捕获、过滤器大全分享给你

Wireshark,这款广受欢迎的开源网络协议分析工具,堪称网络管理员、网络安全专家和网络爱好者的“显微镜”!它可以帮助我们捕获网络中的数据包,并提供详细的解码和分析能力。本文将为您全面剖析 Wireshark 的强大功能,从基础到高级,让你快速掌握这个神器!💡Wireshark 是一款功能强大的网络协议分析器,最早由 Gerald Combs 于 1998 年开发,经过多年发展,已成为网络诊断和分析的首选工具。

网络技术联盟站 724

OSG第三方库编译之二十一:minizip编译(Windows、Linux、Macos环境下编译)

OSG编译;第三方库;跨平台编译;minizip编译(Windows、Linux、Macos环境下编译)

LuckyHanMo的博客 2619

LeetCode 146. LRU 缓存机制(LRU Cache)

易错点正确做法说明更新数据未移动位置保证“最近使用”put 超过容量未淘汰使用删除头部get 不存在时返回错误返回-1使用是 Python 实现 LRU 的最简洁且效率最高的方式。同时掌握哈希表 + 双向链表手动实现 LRU 也是面试时加分项。本题考查的是缓存机制 + 数据结构的组合使用,属于高频面试题之一。错误点正确方式删除时未断开前后节点_remove()正确处理 prev/next忘记从cache删除淘汰节点双向链表头尾更新顺序错误始终更新 prev/next 的双向性。

2501_91641025的博客 1069

三维重建/SLAM笔记_最佳学习路线/攻略

本文目的 学习三维重建,需要不少知识,而知识之间又存在不同的依赖关系,为了后来人能少走弯路,也为了对自己的知识框架系统总结,特记录自己的学习路线。 学习心得 关于读书,了解基础知识是一方面,产生疑问也是一方面,为以后知道学习什么留下印象; 一回生,二回熟,看不懂时,大多是因为先验知识不够,没关系,多查多想,总会搞懂的。 学习路线 1.计算机图形学基础(OpenGL基础版)(徐文鹏 清...

惊鸿一博 3290

leetcode146-LRU缓存

leetcode146-LRU缓存

weixin_45799371的博客 724

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Leetcode 146 LRU缓存 的三种解法

请你设计并实现一个满足约束的数据结构。实现LRUCachecapacitykey-1keyvaluekey-valuecapacity函数get和put必须以O(1)的平均时间复杂度运行。// 缓存是 {1=1}// 缓存是 {1=1, 2=2}// 返回 1// 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}// 返回 -1 (未找到)// 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}// 返回 -1 (未找到)// 返回 3// 返回 4。

freeRoad 875

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

上一篇: Leetcode(210)——课程表 II
下一篇: Leetcode(455)——分发饼干
486L
博客等级 码龄7年 20粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值