题目描述
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存。
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组【关键字-值】。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
题解
LRU 哈希表加双向链表实现。由于单独使用双向链表没法O(1)时间删除,单独使用chche 哈希表也没有办法记录最近最少访问的元素。只有将两种数据结构结合起来,才能在O(1)时间插入删除更新,更新或在访问时将元素移动到双向链表的头部,并且当cache满的时候,删尾部元素。
哈希表该存储什么内容?
链表结点:存 key 和 对应的数据值。
哈希表的存在,是为了快速访问到存储于双向链表的数据:
key:存双向链表中的 key
value:存双向链表结点的引用。
int get(int key) {
if (key 不存在) {
return -1;
} else {
将数据 (key, val) 提到开头;
return val;
}
}
void put(int key, int val) {
node = new DLinkedNode(key, val);
if (key 已存在) {
把旧的数据删除;
将新节点 x 插入到开头;
} else {
if (cache 已满) {
删除链表的最后一个数据腾位置;
删除 map 中映射到该数据的键;
}
将新节点 x 插入到开头;
map 中新建 key 对新节点 x 的映射;
}
}
牛客题解
链表删除节点
p->right->left = p->left;
p->left->right = p->right;
在L节点之后插入p节点
p->right = L->right;
p->left = L;
L->right->left = p;
L->right = p;
自定义双向链表
class Solution {
public:
/**
* lru design
* @param operators int整型vector<vector<>> the ops
* @param k int整型 the k
* @return int整型vector
*/
struct DNode{
int key, value;
DNode* left, *right;
DNode(int _key, int _value): key(_key), value(_value), left(nullptr), right(nullptr){}
}*L,*R;
void remove(DNode* p){
p->right->left = p->left;
p->left->right = p->right;
}
void insert(DNode* p){
p->right = L->right;
p->left = L;
L->right->left = p;
L->right = p;
}
int get(int key){
if(hash.count(key) == 0) return -1;
auto p = hash[key];
remove(p);
insert(p);//将当前节点放在双链表的第一位
return p->value;
}
void set(int key, int value){
if(hash.count(key)){//如果key存在,则修改对应的value
auto p = hash[key];
p->value = value;
remove(p);
remove(p);
}
else{
if(hash.size() == n){//如果缓存已满,则删除双链表最右侧的节点
auto p = R->left;
remove(p);
hash.erase(p->key);//更新哈希表
delete p;//释放内存
}
auto p = new DNode(key,value);
hash[key] = p;
insert(p);
}
}
unordered_map<int,DNode*> hash;//<key,DNode>,DNode的值为相应值
int n;
vector<int> LRU(vector<vector<int> >& operators, int k) {
// write code here
L = new DNode(-1,-1);
R = new DNode(-1,-1);
L->right = R;
R->left = L;
n = k;
vector<int> res;
for(auto v: operators){
if(v[0] == 1){
set(v[1],v[2]);
}
else{
res.push_back(get(v[1]));
}
}
return res;
}
};
146. LRU 缓存机制 【c++详细题解】
小林coding LRU更正版本
利用STL的list
理解list的splice这个方法
void splice (iterator position, list& x, iterator i);//把x这个list的i处元素插入到position的位置
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;
}
};
/**
* 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);
*/
复杂度分析
- 时间复杂度:对于 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:
std::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);
void put(int key, int value);
void addToHead(DLinkedNode* node);
void removeNode(DLinkedNode* node);
void moveToHead(DLinkedNode* node);
DLinkedNode* removeTail();
};
int LRUCache::get(int key)
{
if (!cache.count(key)) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
DLinkedNode *node = cache[key];
moveToHead(node);
return node->value;
}
void LRUCache::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 LRUCache::addToHead(DLinkedNode *node)
{
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void LRUCache::removeNode(DLinkedNode *node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
void LRUCache::moveToHead(DLinkedNode *node)
{
removeNode(node);
addToHead(node);
}
DLinkedNode * LRUCache::removeTail()
{
DLinkedNode *node = tail->prev;
removeNode(node);
return 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);
*/
参考
LRU缓存机制
LRU 策略详解和实现labuladong
【中规中矩】LRU 哈希表加双向链表实现
146. LRU缓存机制
146. LRU缓存机制 C++ 使用unordered_map + list