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 ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 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

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多调用 2 * 105getput

题解

  • 使用双链表模拟缓存 。链表头部作为缓存入口 (插入节点),用于保存最近刚被访问的页面 ;链表尾部作为缓存出口 (删除节点),用于删除最近最久没有被访问的页面

  • 为了便于链表节点的查找,需要定义一个哈希表,用于保存key和对应节点的关系

  • get函数用于模拟cpu对缓存的访问,也就是对内存页的访问。

    • 由LRU算法原理可知,每访问一次缓存,如果能够命中该页面,则说明该页面刚刚被访问过,因此需要将该节点(页面)移动到链表头部
  • put函数用于模拟向缓冲中插入页面。

    • 每次向缓存中插入页面时,都需要首先检查该页面是否在缓存中
    • 如果不在则直接将该页面插入到链表头部(因为该页面刚刚被访问过),同时判断此时缓冲中的大小是否已经超过容量,如果超过则需要从缓存中删除该节点(页面)
    • 如果页面在缓存中,则直接将该页面放入链表头部
cpp 复制代码
struct DLinkNode
{
    int _key,_val;
    struct DLinkNode* prev,*next;
};

class LRUCache {
private:
    unordered_map<int,DLinkNode*> _mp;
    DLinkNode* head,*tail;
    int _size;
    int _capacity;

public:
    LRUCache(int capacity):_capacity(capacity),_size(0) {
        head=new DLinkNode(0);
        tail=new DLinkNode(0);
        head->next=tail;
        tail->prev=head;
    }
    
    int get(int key) {
        auto it=_mp.find(key);
        if(it==_mp.end())
        {
            return -1;
        }else{
            moveHead(it->second);
            return it->second->_val;
        }
    }
    
    void put(int key, int value) {
        auto it=_mp.find(key);
        if(it==_mp.end())
        {
            DLinkNode* e=new DLinkNode(key,value);
            addHead(e);
            _size++;
            _mp[key]=e;
            if(_size>_capacity)
            {
                DLinkNode* tmp=tail->prev;
                removeNode(tmp);
                _mp.erase(tmp->_key);
                delete tmp;
                _size--;
            }
        }else{
            it->second->_val=value;
            moveHead(it->second);
        }
    }

    void removeNode(DLinkNode* node)
    {
        node->prev->next=node->next;
        node->next->prev=node->prev;
    }

    void addHead(DLinkNode* node)
    {
        node->prev=head;
        node->next=head->next;
        head->next->prev=node;
        head->next=node;
    }

    void moveHead(DLinkNode* node)
    {
        removeNode(node);
        addHead(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);
 */
相关推荐
学不动CV了17 分钟前
数据结构---链表结构体、指针深入理解(三)
c语言·arm开发·数据结构·stm32·单片机·链表
百年孤独_2 小时前
LeetCode 算法题解:链表与二叉树相关问题 打打卡
算法·leetcode·链表
Tanecious.5 小时前
LeetCode 876. 链表的中间结点
算法·leetcode·链表
在肯德基吃麻辣烫7 小时前
《Redis》缓存与分布式锁
redis·分布式·缓存
先睡12 小时前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
杰克尼18 小时前
BM5 合并k个已排序的链表
数据结构·算法·链表
努力写代码的熊大1 天前
单链表和双向链表
数据结构·链表
CodeWithMe1 天前
【Note】《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存
linux·spring·缓存
Orlando cron1 天前
数据结构入门:链表
数据结构·算法·链表
大春儿的试验田1 天前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存