LeeCode_146. LRU 缓存机制(map+双向链表、map+栈+双端队列)

一、介绍

1.题目描述

题目链接:

https://leetcode-cn.com/problems/lru-cache/

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

2.测试样例

["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","put","put","get","put","put","get"]
[[2],[2,1],[2,2],[2],[1,1],[4,1],[2]]
# [null,null,null,2,null,null,-1]

["LRUCache","put","put","get","put","get","put","get","get","get"]
[[2],[1,0],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]
# [null,null,null,0,null,-1,null,-1,3,4]

["LRUCache","put","put","get","put","put","get"]
[[2],[2,1],[2,2],[2],[1,1],[4,1],[2]]
# [null,null,null,2,null,null,-1]

二、题解

1、map+双向链表

链表:链表可以实现对节点的快速插入和删除操作

map:map映射可以将键值对进行匹配

在这道题的问题中,我们需要插入键值对,又需要对数据进行删除,联想到链表+map的组合。其中map键为key,值为链表的一个节点。一个节点将存储键和值,方便数据处理。

由于删除的位置是随机的,因此需要使用双向循环链表

kval*pre*next
Nodekeyvalue初始为node初始为node
keyvalue
mapkeyNode *p

思路

对于最近最少使用算法,若一直插入元素,最早被插入的元素应最先被删除。

由这一特性,可以想到若通过put更新/插入元素,或通过get获取元素,则表示该元素最近被使用过,为保证顺序,应将其原位置删除,重新插到末尾。

这种删除插入操作用双向链表实现,由map找到对应节点位置,删除该节点,重新插入末尾。

当链表满后,头结点即为最近最少使用,直接删除,然后在末尾插入新节点。

当操作为LRUCache

  • 初始化链表头结点node,pre和next都指向自己,构成双向循环

当操作为put

  • 若不存在该键
    • 若存储未满,插入到node前【相当于链表末尾】
    • 若存储已满,删除node后的节点【即链表头】,将新节点插入到node前【相当于链表末尾】
    • 创建map映射
  • 若存在该键
    • 链表中删除该节点,在最后重新插入该节点,map更新映射

当操作为get

  • 若不存在,返回-1
  • 若存在,将该节点删除,在末尾重新插入【保证最近最少使用的删除顺序】

注意事项

  • 由于各个环节都会用到插入操作,可以将插入封装成一个函数
  • put中每个可能性最后都要在链表末尾插入元素,并更新map,可以统一运行
class LRUCache {
    int n,num=0;
    struct Node{
        int val,k;
        Node *next,*pre;
    };
    Node* node = new Node;
    map<int,Node*> mp;
public:
    LRUCache(int capacity) {  // 初始化循环链表
        n=capacity;
        node->next=node;
        node->pre=node;
    }
    
    int get(int key) {
        if(mp.count(key)){  // 如果存在,更新并返回
            Node *p=mp[key];         // 找到并删除该节点
            p->pre->next=p->next;
            p->next->pre=p->pre;
            inser(node,p);  // 重新在末尾插入该节点
            return p->val;
        }
        else return -1;
    }
    
    void put(int key, int value) {
        Node *p=new Node();  
        p->val=value;
        p->k=key;
        if(mp.count(key)){  // 如果已存在,更新
            p=mp[key];     // 找到并删除该节点
            p->pre->next=p->next;
            p->next->pre=p->pre;
            p->val=value;
        }
        else if(num>=n){   // 如果已满,删除第一个节点,在最后插入节点
            mp.erase(node->next->k);   // map删除第一个节点
            node->next=node->next->next;   // node删除第一个节点
            node->next->pre=node;
        }
        else num++;
        inser(node,p);  // 在node前面插入新节点
        mp[key]=p; // 更新map
    }

    void inser(Node* node, Node *p){   // 在node前面插入新节点
        node->pre->next=p;
        p->pre=node->pre;
        p->next=node;
        node->pre=p;
    }
};

image-20210924143040998

2、map+双端队列+栈🟢(超时)

还有一种时间复杂度为O(n)的方法,但是超时了。

其使用到的数据结构:map+双端队列+栈,其中栈和队列存储键即可,用于调整顺序

当操作为LRUCache

  • 初始化容量

当操作为put

  • 若不存在该键
    • 若存储未满,插入到队列末尾
    • 若存储已满,删除队列头,新键插入队列尾
    • 更新map
  • 若存在该键【需要将该键重新插入到末尾,由于队列不能直接去中间的元素,所以借助栈】
    • 从前向后弹出元素并入栈,直到遇到该键
    • 将该键弹出,在末尾重新插入
    • 将栈中元素弹出并在队列头插入
    • 更新map

当操作为get

  • 若不存在,返回-1
  • 若存在,将该节点删除,在末尾重新插入【保证最近最少使用的删除顺序】
class LRUCache {
    int n;
public:
    LRUCache(int capacity) {
        n=capacity;
    }
    
    int get(int key) {
        if(mp.count(key)){  // 如果存在,更新并返回
            while(q.front()!=key){
                stk.push(q.front());
                q.pop_front();
            }
            q.pop_front();
            q.push_back(key);
            while(!stk.empty()){
                q.push_front(stk.top());
                stk.pop();
            }
            return mp[key];
        }
        else return -1;
    }
    
    void put(int key, int value) {
        if(mp.count(key)){  // 如果已存在,更新
            mp[key]=value;
            while(q.front()!=key){
                stk.push(q.front());
                q.pop_front();
            }
            q.pop_front();
            q.push_back(key);
            while(!stk.empty()){
                q.push_front(stk.top());
                stk.pop();
            }
        }
        else if(mp.size()<n){   // 如果未满
            mp[key]=value;
            q.push_back(key);
        }
        else{   // 如果已满
            mp.erase(q.front());
            mp[key]=value;
            q.pop_front();
            q.push_back(key);
        }
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值