原题是这样的:
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
直观的想法就是用一个hash_map存储key-value对。平均情况下get和set的复杂度都是常数时间。
然而这里的问题是在set的时候,如果cache的容量满了,需要将最久没使用的块清除,如何做才能使这一个操作复杂度尽可能低呢?
我起初在想是否可以建立一个小顶堆,来存放以“使用次数”为key的结点,这样一来,每次要清除一个“醉酒未使用”的块的时候,只要把小顶堆的堆顶元素去掉,
再对堆进行调整就可以了,这样做的复杂度是O(logn)。然而问题是,每次get一个块的时候,需要对堆中对应的结点的key,也就是使用次数进行更新,这样就涉及到
一个查询——即通过传给get的参数key到堆里面找到对应的结点,再更新值。对堆查询?还是需要一定的时间开销。
能不能避免这样的查询呢?
想到这里,其实我自己犯了一个错误——“最久未使用”并不等于“使用次数最少”。比如两个cache中的块A和B,A被get了1次,B被get了1000次。但是A是最新一次被get的,而B可能是在很久之前被get到1000次之后就再也没有被get了。那么其实这里“最久未被使用”的块应当是B。
所以之前我在脑子里面想的用堆去存放使用次数之类的想法纯粹就是庸人自扰。根据对“最久未使用”的正确的理解,只需要维护一个链表,结点值为块对应的key。每次get一个块,就把它移到链表的头部。要去除的“最久未使用”的块,始终位于链表底部(当然也可以反过来,把最新使用的放到链表尾,那么第一个节点始终就是最久未使用的了)。
我总是把问题想得过于复杂(当然这次根本就是想错了= =)。我的思维方式还是有很大的优化空间啊!
下面是我的代码,可以AC:
typedef struct node{
int key;
int value;
node * prev;
node * next;
}Node;
class link{
Node * head;
Node * tail;
int size;
public:
link(){
head = NULL;
tail = NULL;
size = 0;
}
void push_back(Node * n){
if(head == NULL){
head = n;
tail = n;
n->prev = NULL;
n->next = NULL;
}
else{
tail->next = n;
n->prev = tail;
n->next = NULL;
tail = n;
}
}
void MoveToTail(Node * n){
if(n->next == NULL)return;
if(n->prev == NULL){
head = n->next;
n->next->prev = NULL;
}
else{
n->prev->next = n->next;
n->next->prev = n->prev;
}
tail->next = n;
n->prev = tail;
n->next = NULL;
tail = n;
}
void RemoveNode(Node * n){
if(n->prev == NULL){
head = n->next;
if(n->next != NULL)
n->next->prev = NULL;
else
tail = NULL;
free(n);
}
else{
n->prev->next = n->next;
if(n->next != NULL)
n->next->prev = n->prev;
free(n);
}
}
Node * GetHead(){
return head;
}
};
class LRUCache{
public:
int capacity;
int count;
link datas;
unordered_map<int, Node *> maps;
LRUCache(int capacity) {
this->capacity = capacity;
count = 0;
}
int get(int key) {
node * n;
try{
n = maps.at(key);
}catch(out_of_range e){
return -1;
}
datas.MoveToTail(n);
return n->value;
}
void set(int key, int value) {
Node * n;
try{
n = maps.at(key);
}catch(out_of_range e){
if(this->count == capacity){
n = datas.GetHead();
maps.erase(n->key);
datas.RemoveNode(n);
count--;
}
Node * newdata = new Node();
newdata->key = key;
newdata->value = value;
newdata->next = NULL;
datas.push_back(newdata);
maps.insert(pair<int, Node *>(key, newdata));
count++;
return;
}
n->value = value;
datas.MoveToTail(n);
}
};