LRU, also known as "Least Recently Used", is a cache algorithm which discards the the least recently used entry when we push a new entry to the cache.
This problem wants us to implement two function "int get(int key)" and "void set(int key, int value)" which means to get a record and add a new record in the cache(if the cache is full, remove the least recently used one). The hardest part is how to track which record is used and when.
I have implemented two version of solutions which both can get an "Accepted".
The first one uses std::map and std::list to track the records. The std::map contains the pair of <key, pointer_to_value>, at the same time, the std::list stores the values and the list is ordered by the recent used time.
class LRUCache{
public:
struct Entry {
int key, value;
Entry(){}
Entry(int ikey, int ivalue): key(ikey), value(ivalue) {}
};
LRUCache(int icap): capacity(icap) {}
int get(int key) {
if (mp.find(key) == mp.end()) {
return -1;
}
int res = mp[key] -> value;
move_front(key);
return res;
}
void set(int key, int value) {
if (mp.find(key) != mp.end()) {
mp[key] -> value = value;
move_front(key);
} else {
Entry res(key, value);
if (lst.size() >= capacity) {
pop_back();
}
lst.push_front(res);
mp[key] = lst.begin();
}
}
private:
map<int, list<Entry>::iterator> mp;
list<Entry> lst;
int capacity;
void move_front(int key) {
//ASSERT_NE(mp.find(key), mp.end())
Entry e = *mp[key];
auto iter = mp[key];
lst.erase(iter);
lst.push_front(e);
mp[key] = lst.begin();
}
void pop_back() {
Entry e = *(--lst.end());
mp.erase(e.key);
lst.pop_back();
}
};
The second version only use one std::map the maintain all these record by implementing a double-linked list of the nodes of std::map, of course the list is sorted by the recent used time. Each node in the std::map contains a "previous" and a "next" record to indicate the key of the adjacent node in the list. And we also need a top/bottom pointer to indicate the first and last node of the list.
However, the second version is more complicated that I won't use that code in the interview.
class LRUCache {
public:
LRUCache(int icap);
int get(int key);
void set(int key, int value);
void show();
private:
struct entry {
int value, pre, next;
entry(){}
entry(int ivalue, int ipre, int inext): \
value(ivalue), pre(ipre), next(inext){}
};
void cache_remove(int key);
void cache_add(int key, int value);
size_t cap;
map<int, entry> mp;
int top, buttom;
static const int INVALID = -1;
};
LRUCache::LRUCache(int icap): cap(icap)
{
top = buttom = INVALID;
mp.clear();
}
void LRUCache::show()
{
int now = top;
while (now != INVALID) {
auto iter = mp.find(now);
printf("<%d, %d>", now, (iter -> second).value);
now = (iter -> second).next;
}
puts("");
}
void LRUCache::cache_remove(int key)
{
if (key == INVALID) {
return;
}
auto iter = mp.find(key);
int pre = (iter -> second).pre;
int next = (iter -> second).next;
if (pre != INVALID) {
(mp.find(pre) -> second).next = next;
}
if (next != INVALID) {
(mp.find(next) -> second).pre = pre;
}
if (key == buttom) {
buttom = pre;
}
if (key == top) {
top = next;
}
mp.erase(iter);
}
void LRUCache::cache_add(int key, int value)
{
entry e(value, INVALID, top);
(mp.find(top) -> second).pre = key;
mp.insert(make_pair<int, entry>(int(key), entry(e)));
if (mp.size() > this -> cap) {
cache_remove(buttom);
}
top = key;
if (mp.size() == 1) {
this -> buttom = key;
}
}
int LRUCache::get(int key)
{
if (mp.find(key) == mp.end()) {
return -1;
}
int value = (mp.find(key) -> second).value;
cache_remove(key);
cache_add(key, value);
return value;
}
void LRUCache::set(int key, int value)
{
if (mp.find(key) != mp.end()) {
cache_remove(key);
}
cache_add(key, value);
}
The time complexity of the algorithm is as same as the get/set function of the std::map.