题目描述:
运用你所掌握的数据结构,设计和实现一个 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
思路:这里采用的是使用双向链表和hash_map结合的思想,hash_map可以保证查找的时间复杂度是O(1),双向链表可以满足增删的时间复杂度是O(1)。因此我们只需要采用一种很好的结构,使得,我们在知道key的时候,就知道其对应的节点,从而做出修改。
首先我们定义的双向链表的节点如下:
struct linkNode{
int key;
int val;
linkNode *next, *pre;
linkNode(int k, int v): key(k), val(v), next(NULL), pre(NULL) {};
};
然后我们用一个定义一个哈希表,把对应的key和链表的节点映射出来。
对于最近最少的数据,我们可以采用这样的机制,我们用双向链表表示一个数据表,靠近链表尾部的节点,说明该节点距离上次活动的时间越近,上次活动可以是插入,也可以是获取。知道了这个基本思路之后,我们的具体步骤如下:
1. 当get数据的时候,首先判断这个数据是否在链表中(通过哈希表实现),如果在,则返回这个数据,并且把这个数据调整到链表的末尾,这个是通过结构中的指针实现;如果不在则返回-1
2. 当put数据的时候,首先判断这个数据是否在链表中,如果在,那么则更新这个数据的value值,并且把该节点移到末尾;如果不在链表中,先判断链表现在是否已经满了,如果满了,则删除链表头部的数据,在链表末尾处加入新的数据,同时删除在哈希表中对应的key,如果没有满的话,就加入到链表尾。再把这个节点加入到哈希表中。
代码:
struct linkNode{//双向链表,需要在尾部插入
int key;
int val;
linkNode *next, *pre;
linkNode(int k, int v): key(k), val(v), next(NULL), pre(NULL) {};
};
class LRUCache {
public:
LRUCache(int capacity) {
cap = capacity;
link = new linkNode(-1, -1);
end = link;
}
linkNode * adjestLink(linkNode *head){
if(head->next != NULL){
head->pre->next = head->next;
head->pre->next->pre = head->pre;
head->next = NULL;
end->next = head;
head->pre = end;
end = end->next;
}
//else说明本来就在末尾处,不需要修改
return head;
}
int get(int key) {
if(key2value.find(key) != key2value.end()){//说明原来的map里面有这个数,需要调整链表
linkNode *p = key2value[key];//真正的头指针
key2value[key] = adjestLink(p);
return key2value[key]->val;
}
return -1;
}
void put(int key, int value) {
if(key2value.find(key) != key2value.end()){//说明原来的map里面有这个数,需要调整链表以及修改hash_map
linkNode *p = key2value[key];//真正的头指针
key2value[key] = adjestLink(p);
key2value[key]->val=value;
}
else{//说明可以直接插入,需要判断是否链表已经满了
if(key2value.size() == cap){
linkNode *p = link->next;//真正的头指针
int delKey = p->key;
if(p->next==NULL){
p = new linkNode(key, value);
end->pre->next = p;
p->pre = end->pre;
end = p;
}
else{
p->pre->next = p->next;
p->pre->next->pre = p->pre;
free(p);
p = new linkNode(key, value);
end->next = p;
p->pre = end;
end = end->next;
}
key2value.erase(delKey);
key2value[key] = p;
}
else{
linkNode *p = new linkNode(key, value);
end->next = p;
p->pre = end;
end = end->next;
key2value[key] = p;
}
}
}
private:
int cap;
unordered_map<int, linkNode *> key2value;
linkNode *link;
linkNode *end;
};