LeetCode 热题 100_LRU 缓存(35_146)
- 题目描述:
- 输入输出样例:
- 题解:
- 解题思路:
- 代码实现(思路一(哈希表 + 双向链表)):
- 部分代码解读
题目描述:
请你设计并实现一个满足 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) 的平均时间复杂度运行。
输入输出样例:
示例 :
输入
[“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 * 105 次 get 和 put
题解:
解题思路:
思路一(哈希表 + 双向链表):
1、通过题目分析,put操作:如果key不在缓存中那我们需要进行结点的插入操作,若插入时缓存已满则先删除最久未访问的结点再插入。这里我们可以想到双向链表,头部存储最近访问结点,尾部存储最久未访问结点。get操作,若存在key则返回结点的value,这里get相当于一个查找,所以我们可以想到哈希表,这样我们就能快速的进行结点的查找和定位。
2、具体思路如下:
① 我们创建一个头结点和一个额外的尾结点来方便结点的插入和删除操作。
② 插入操作(put):我们将刚插入的元素或者最近使用的元素放在链表的头部,则尾部为最长时间未使用的元素。
若要插入结点key时,之前存在序号为key的结点则移到链表头部。
若要插入节点key时,之前不存在序号为key的结点,且结点数未满则插入链表头部,若结点数已满则插入后删除尾结点。
③ 获取操作(get):分析到获取我们很快能想到哈希表,因哈希表能让我们快速的进行查找操作,所以上述插入和删除时需要维护一个哈希表。
若结点不在哈希表中则返回-1。
若存在哈希表中则返回值,并将结点移动到头部。
力扣官方题解链接(有缓存 get() 和put () 过程图很不错)
3、复杂度分析
① 时间复杂度:对于 put 和 get 都是 O(1)。
② 空间复杂度:O(capacity),因为哈希表和双向链表最多存储 capacity+1 个元素。
代码实现(思路一(哈希表 + 双向链表)):
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;
//head和tail方便结点的操作
DLinkedNode* head;
DLinkedNode* tail;
//size是当前缓存中的结点数,capacity是缓存最大的容量
int size;
int capacity;
public:
//初始化缓存,缓存容量为 _capacity,一开始缓存存入size=0个结点
LRUCache(int _capacity):capacity(_capacity),size(0){
head=new DLinkedNode();
tail=new DLinkedNode();
head->next=tail;
tail->prev=head;
}
//如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
int get(int key){
if(!cache.count(key)){
return -1;
}
//若存在key,则将key对应的结点移动到链表首部,先拆出来,再添加到首部
removeNode(cache[key]);
addToHead(cache[key]);
return cache[key]->value;
}
void put(int key,int value){
//如果关键字 key 已经存在,则变更其数据值 value
if(cache.count(key)){
removeNode(cache[key]);
addToHead(cache[key]);
cache[key]->value=value;
//如果不存在,则向缓存中插入该组 key-value
}else{
DLinkedNode *newNode=new DLinkedNode(key,value);
cache[key]=newNode;
addToHead(newNode);
++size;
//如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
if(size>capacity){
DLinkedNode *removed=removeTail();
cache.erase(removed->key);
delete removed;
--size;
}
}
}
//插入链表头部
void addToHead(DLinkedNode *node){
node->next=head->next;
node->prev=head;
head->next->prev=node;
head->next=node;
}
//断开链表尾部(需返回,用于删除哈希表中对应数据)
DLinkedNode *removeTail(){
DLinkedNode *node=tail->prev;
tail->prev=node->prev;
node->prev->next=tail;
return node;
}
//断开结点
void removeNode(DLinkedNode* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
};
部分代码解读
构造函数声明+初始化列表进行变量初始化和赋值
//下方代码的用法和第一行相同,是构造函数初始化列表,对变量初始化和赋值
DLinkedNode(int _key,int _value):key(_key),value(_value),prev(nullptr),next(nullptr){}
//构造函数
//初始化 capacity 成员变量 为传递给构造函数的参数 _capacity
//初始化 size 成员变量 为 0,表示缓存初始化时为空。
LRUCache(int _capacity):capacity(_capacity),size(0){}
LeetCode 热题 100_LRU 缓存(35_146)原题链接
欢迎大家和我沟通交流(✿◠‿◠)