原题如下:
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
这题确实是LeetCode题中的一股清流,少见的大规模考验数据结构(注意他题中说的O(1))
所以如何搭建合适的数据结构是需要慎重思考的
1.先说 get()函数吧:
1)要想查一个 key 所对应的value(题中说的有点模糊),达到(O(1)),当然是Hash表啦,所以你至少需要一个hash_map<int,type> my_map;注意这个type是否一定就是一个int(value)??
2)由get()查找一个key 查不到倒好,一旦查到,那么这个key就变成了最近一个使用的(未来size(满了)删除的话,它必然不是最好的一个选择) 所以由get()和LRU的删除机制,你还需要一个模拟队列!!!(注意这并不是个纯粹的队列,当访问到队列的一个元素的时候你需要把他排到队列的末尾,当LRU size满的时候 需要从模拟队列的头部拿掉一个,再在末尾接上新的节点)
2.再说put()函数:
1)如果 LRU的size 没有满 并且 里面的key不存在的情况
(比如 size=10【1,10】【2,15】 put(3,18))
这种最简单,直接把这个 key 放到 hash_map 里面去
再在模拟队列的后面接上新的key
2)如果 LRU内部已经存在了key
(比如 size=10【1,10】【2,15】 put(2,18))
这种,需要把模拟队列里对应的key 移动到末尾(注意这个移动操作,在get()函数里一旦访问到的话,我们也是要用同样的移动操作,所以可以单独写一个函数),再把hash_map 里 key 所对应的 value???改过来。
3)如果 LRU的size 已经满了 并且 hash_map 里面 还没有这个key:
(比如 size=2【1,10】【2,15】 put(3,18))
这时候,我们的模拟队列就派上用场了,要把他的第一个元素个拿掉(hash_map里面的key也要删除掉),然后 把新的key 接在模拟队列的末尾,并且把 key 插入到 hash_map里面。
3.考虑我们所需要构造的数据结构
模拟队列!!!???并不是真正意义上的模拟队列(有移动操作),而且对每个操作都要达到O(1) 所以对这个模拟队列 给个key 你需要立马知道 他在哪个部位!!!!(所以重点到了,hash_map<int,type>这个 type 不仅仅 要与 value对应,还要与 他在 模拟队列的position 位置对应,即 一个 key 唯一对应 一个int外加一个在模拟队列的position),
有人可能想到用 vector来做这个模拟队列,一个position 正好对应于 vector【i】的i下标,但是这种方法有个弊端,那就是当删除(或作移动到末尾操作)一个下标i之后,i后面的所有的下标 他们都要在自己的基础上-1,即hash_map 里面的这些key对应的 position 也都要减1,一下就上升到O(n)了,
所以用指针记录position 是不二之选,对一个key而言,他的position就是一个指针入口,瞬间就可以找到他在模拟队列的位置,所以这个模拟队列最好用 链表
但是我们为什么不要去用单链表!!,如果找到一个 要删除的 key 对应的position 我们需要需要重新遍历到 position的前面一个位置!!!
所以综上,我们的模拟队列最好用,双链表,每个节点都有一个next 一个front 域,这个双链表 还带有头结点,尾指针(方便操作的东西)
代码如下(注意我用的是map 查找速度是对数级的,看客可以自行改为hash_map查找速度O(1)):
struct double_Listnode
{
int val;
double_Listnode *front, *next;
double_Listnode(int x) :val(x), front(NULL), next(NULL){};
};
struct double_List
{
double_Listnode *head, *tail;
};
struct map_nodes{
int value;
double_Listnode *position;
};
struct new_struct{
map<int, map_nodes>my_map;//这里可以用 hash_map get()函数就会降到O(1)
int max_size;
double_List my_select;
};
class LRUCache
{
public:
new_struct my_struct;
LRUCache(int capacity){
my_struct.max_size = capacity;
my_struct.my_select.head = new double_Listnode(-1);
my_struct.my_select.tail = my_struct.my_select.head;//带头结点的双链表
}
void make_key_to_back(int key){
double_Listnode *need_back = my_struct.my_map[key].position;
if (need_back != my_struct.my_select.tail){//因为带头结点 所以 第一个有效元素的 need_back 不用单独考虑
need_back->front->next = need_back->next;
need_back->next->front = need_back->front;//need_back 前后节点的变更
my_struct.my_select.tail->next = need_back;
need_back->front = my_struct.my_select.tail;
need_back->next = NULL;
my_struct.my_select.tail = need_back;//only tail 的变更
}
}
int get(int key){
if (my_struct.my_map.find(key) == my_struct.my_map.end())
return -1;
else {
make_key_to_back(key);
return my_struct.my_map[key].value;
}
}
void erase_first(){
double_Listnode *need_erase = my_struct.my_select.head->next;//注意不是删除head 是head->next
int need_erase_key = need_erase->val;
map<int, map_nodes>::iterator p = my_struct.my_map.find(need_erase_key);
p = my_struct.my_map.erase(p);
if (need_erase->next){
my_struct.my_select.head->next = need_erase->next;
need_erase->next->front = my_struct.my_select.head;
}
else{
my_struct.my_select.tail = my_struct.my_select.head;
my_struct.my_select.head->next = NULL;
}
need_erase->next = NULL; need_erase->front = NULL;
delete need_erase;
}
void insert_my_map(int key, int value){
my_struct.my_map[key].value = value;
double_Listnode *back_position = new double_Listnode(key);
my_struct.my_select.tail->next = back_position;
back_position->front = my_struct.my_select.tail;
back_position->next = NULL;
my_struct.my_select.tail = back_position;
my_struct.my_map[key].position = back_position;
}
void put(int key, int value){
if (my_struct.my_map.size() >= my_struct.max_size && my_struct.my_map.find(key) == my_struct.my_map.end()){
erase_first();//map 满了 要删除
insert_my_map(key, value);
}
else if (my_struct.my_map.find(key) != my_struct.my_map.end()){
make_key_to_back(key);
my_struct.my_map[key].value = value;
}
else insert_my_map(key, value);
}
};