当前位置:网站首页>Leetcode(146)——LRU 缓存
Leetcode(146)——LRU 缓存
2022-06-24 19:03:00 【SmileGuy17】
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
提示:
- 1 1 1 <= capacity <= 3000 3000 3000
- 0 0 0 <= key <= 10000 10000 10000
- 0 0 0 <= value <= 1 0 5 10^5 105
- 最多调用 2 ∗ 1 0 5 2 * 10^5 2∗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 ( c a p a c i t y ) O(capacity) O(capacity),因为哈希表和双向链表最多存储 2 ∗ capacity + 2 2*\text{capacity} + 2 2∗capacity+2 个元素。
边栏推荐
- [go Language brossage] go from 0 to Getting started 4: Advanced use of slice, Primary Review and Map Getting started Learning
- 两位湖南老乡,联手干出一个百亿IPO
- Compressed list of redis data structures
- 别再用 System.currentTimeMillis() 统计耗时了,太 Low,StopWatch 好用到爆!
- Set up your own website (14)
- 大一女生废话编程爆火!懂不懂编程的看完都拴Q了
- Redis installation of CentOS system under Linux, adding, querying, deleting, and querying all keys
- Stackoverflow 年度报告 2022:开发者最喜爱的数据库是什么?
- Five day summary of software testing
- Predicate
猜你喜欢

网络安全审查办公室对知网启动网络安全审查,称其“掌握大量重要数据及敏感信息”

"Ningwang" was sold and bought at the same time, and Hillhouse capital has cashed in billions by "selling high and absorbing low"

Audio and video 2020 2021 2022 basic operation and parameter setting graphic tutorial

16个优秀业务流程管理工具

Implement the redis simple client customized based on socket

微信小程序自定义tabBar

Information theory of popular science Shannon

1、 Downloading and installing appium

Design of routing service for multi Activity Architecture Design

《梦华录》“超点”,鹅被骂冤吗?
随机推荐
C language to realize mine sweeping (simple version)
Wait for the victory of the party! After mining ebb tide, graphics card prices plummeted across the board
科技抗疫: 运营商网络洞察和实践白皮书 | 云享书库NO.20推荐
用手机摄像头就能捕捉指纹?!准确度堪比签字画押,专家:你们在加剧歧视
Apple doesn't need money, but it has no confidence in its content
Predicate
首个大众可用PyTorch版AlphaFold2复现,哥大开源OpenFold,star量破千
Simulation lottery and probability statistics experiment of the top 16 Champions League
Bytebase 加入阿里云 PolarDB 开源数据库社区
"Super point" in "Meng Hua Lu", is the goose wronged?
JVM tuning
Map跟object 的区别
二叉树的基本性质与遍历
Todesk remote control, detailed introduction and tutorial
开放可编程基础设施(OPI)项目,重新定义DPU/IPU
C語言實現掃雷(簡易版)
《梦华录》“超点”,鹅被骂冤吗?
Apache+php+mysql environment construction is super detailed!!!
You can capture fingerprints with a mobile camera?! Accuracy comparable to signature and monogram, expert: you are aggravating discrimination
What is showcase? What should showcase pay attention to?