题意
请你设计并实现一个满足 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) 的平均时间复杂度运行。
题解
要求get和put都要以O(1)的平均时间复杂度运行。
已知:哈希表(unordered_map<int, MyNode*>)查找get(),增,改put()为O(1),但是无顺序,由题意看出我们需要记录进入缓存的先后。因此还需要一个数据结构来记录先后顺序。
可以实现一个双向链表来完成队列的作用,最新插入的可以在队尾插入,而达到容量需要删除的则是队首的元素。因此刚好可以用双向链表来完成队列模拟。
最关键的是模拟双向链表如何实现,因为哈希表可以直接使用unorder_map来实现。
1、定义节点的类:需要定义两个指针一个指向前、一个指向后
class MyNode
{
public:
int key;
int val;
MyNode* prev;
MyNode* next;
MyNode(): key(0), val(0), prev(nullptr), next(nullptr){}
MyNode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr){}
};
2、定义根据节点来实现的双向链表的功能:增、删头,改,删任意点的功能
- 定义公有的变量:头结点指针、尾节点指针
- 定义构造函数:初始化头结点和尾节点:在堆上面创建,并互相指。
- 定义增节点函数,需要传入节点指针:增加就是在尾部增加,因此tail->prev才是尾节点其next指针指向新的节点,新节点的prev指针指向tail->prev(之前的尾节点),新节点的next指针指向tail尾巴,tail的prev指针指向新节点。
- 定义删头节点函数:要先判断是否存在头结点,如果头结点不存在(if(head->next == tail->prev))则返回-1,否则删除头结点:先保存头结点的键用于擦除哈希表里面的节点,再用普通的删除节点函数删掉head->next节点。
- 定义修改节点函数:删节点函数 + 增加节点函数
- 定义删节点函数:传入删除的节点,让待删节点的前一个节点的next指针指向待删节点后一个节点。让待删节点后一个节点的prev指针指向待删节点的前一个节点即
node->prev->next = node->next;
node->next-prev = node->prev;
class Myqueue
{
public:
MyNode* head;
MyNode* tail;
Myqueue(){
head = new MyNode();
tail = new MyNode();
head->next = tail;
tail->prev = head;
}
void add_node(MyNode* node)
{
tail->prev->next = node;
node->prev = tail->prev;
node->next = tail;
tail->prev = node;
}
int remove_head()
{
if(head->next == tail) return -1;
int head_key = head->next->key;
remove_node(head->next);
return head_key;
}
void move_node(MyNode* node)
{
remove_node(node);
add_node(node);
}
void remove_node(MyNode* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
};
3、定义好了我们需要的队列(双向链表),接下来就是使用它来完成我们的LRU缓存机制模拟:
- 定义私有的变量:capacity、哈希表:unordered_map<int, MyNode*>、队列:Myqueue。(定义为私有变量的原因防止外部直接修改,保证缓存的一致性和正确性)
- 在构造函数中,如果形参和成员变量同名,为了区分它们,可以使用 this 指针来引用当前对象的成员变量。this 指针指向当前对象本身,通过 this-> 可以访问当前对象的成员变量和成员函数。
- 定义LRUCache的构造函数:获取capacity给与私有变量
- get()函数是判断该key是否存在,不存在直接return -1,存在返回键对应的值。实现中:因为存在所以不需要再new一个变量,直接定义MyNode* node = map[key];即可以获取该点并操作该点。获取到该点我们依托私有变量的队列类中的mode_node(node)函数将当次访问过的点放到链尾上,并返回该节点记录的值
- put()函数:传入了key和value的值,首先要判断该key是否已存在于缓存中,如果存在就调用queue类的功能move_node(node)函数。如果不存在就要判断是否map.size()已经达到了capacity了,达到了就得删除头节点并依据queue.removehead()返回的值删掉哈希表里面的值。在调用queue.add_node(node)函数。
class LRUCache {
private:
int capacity;
unordered_map<int, MyNode*> map;
Myqueue queue;
public:
LRUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
if(!map.count(key)) return -1;
MyNode* cur_node = map[key];
queue.move_node(cur_node);
return cur_node->val;
}
void put(int key, int value) {
MyNode* node;
if(map.count(key))
{
node = map[key];
node->val = value;
queue.move_node(node);
}
else
{
if(map.size() == capacity)
{
int head_key = queue.remove_head();
map.erase(head_key );
}
node = new MyNode(key, value);
map[key] = node;
queue.add_node(node);
}
}
};
总的代码
class MyNode
{
public:
int key;
int val;
MyNode* prev;
MyNode* next;
MyNode(): key(0), val(0), prev(nullptr), next(nullptr){}
MyNode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr){}
};
class Myqueue
{
public:
MyNode* head;
MyNode* tail;
Myqueue(){
head = new MyNode();
tail = new MyNode();
head->next = tail;
tail->prev = head;
}
void add_node(MyNode* node)
{
tail->prev->next = node;
node->prev = tail->prev;
node->next = tail;
tail->prev = node;
}
int remove_head()
{
if(head->next == tail) return -1;
int head_key = head->next->key;
remove_node(head->next);
return head_key;
}
void move_node(MyNode* node)
{
remove_node(node);
add_node(node);
}
void remove_node(MyNode* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
};
class LRUCache {
private:
int capacity;
unordered_map<int, MyNode*> map;
Myqueue queue;
public:
LRUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
if(!map.count(key)) return -1;
MyNode* cur_node = map[key];
queue.move_node(cur_node);
return cur_node->val;
}
void put(int key, int value) {
MyNode* node;
if(map.count(key))
{
node = map[key];
node->val = value;
queue.move_node(node);
}
else
{
if(map.size() == capacity)
{
int head_key = queue.remove_head();
map.erase(head_key );
}
node = new MyNode(key, value);
map[key] = node;
queue.add_node(node);
}
}
};