LRU Cache
Question:
思路:
这道题呢, 其实难点就在这个他要求put 和 get 都是O(1), 因为其实如果没有效率的要求, 那这个题就随随便便套用一个unordered_map, 然后胡乱更新就完了.
但他要求了要O(1), 所以说就是我们还要想一个方法,实时track 这个least recently used key.
一个比较直观的方法就是, 我们设置一个container, 然后每次运用到一个key,就把这个key 推到container的最前方, 每次当put需要evict的时候, 我们只要pop掉这个container 里的最后一项就可以了. 因为container里的最后一项肯定就是least recently used key.
然后我们要想一下怎么构建这个container 才能使得我们可以pop 最后一项,然后每次都push到前面. 并且update这个container 必须是O(1).
首先光是满足存储key的container, 我们可以用array, vector, stack or queue. 但是他们几个都没发满足O(1). 因为 我们思考一下. 首先光是evict least recently used key, 这几个container 就都满足不了。array的话 当删掉一个item, 就会多出一个空白, 然后我门需要把array里其他的所有item都调整一个位置. 同理当有新的key被用到, 那么其他所有item都要往后挪一下(以来代表新的key 是最recently used, 其他都是更least recently used), 没法满足O(1). 其次退一步想, 如果我们想实现O(1) access, 我们必须得有一个pointer 来指向对应的key 和 他的顺序,是排第几recently used.但是我们的array 是keep chaning 的, 一个pointer 没法随着array的变化一直指向同一个item. pointer可以指向array里的一个固定位置. 但是当array里的item 变了顺序之后, pointer也得重新指向才能 指到corresponding item.
stack vector queue 同理.
vector 首先erase 就不是O(1), 其次pointer 只能指向vector的某一个位置的address, 并不能随着里面内容的改变pointer跟着变,意思就是每次更新完vector,我还得重新跟新一下pointer的指向.
stack 和 queue 都不支持random access, 就是stack[0], queue[1] 这种都用不了,那就更别提什么用pointer实时更新了. 没办法把stack 和 queue 里中间的item 进行更新.
有一个container: deque 是支持random access 的,但deque 和vector 差不多, 好像比vector 更高级
(待以后需要更细节的学习)
最后揭晓答案: 我们用c++ 的 list.
c++ 的 list 本质上是一个double linked list.
linked list的好处不必多说, pointer可以一直指向同一个地址, 不用担心增加item之后同一个地址会变成不同的item. 指向的某一个item 永远存在一个地址下.
删除least recent 和增加一个新的到front 都可以在O(1) 内完成, 因为是double linked list, 所以可以直接O(1) 删除一个链接或者增加一个链接.
完美
然后key idea 就是我们要先 做一个container “list” 存recent use order, 然后每次cache里加新东西的时候不仅要把key 存在cache 里, 也要把新的key 加进list里来记录顺序,
所以就是我们还用一个hash map 存key-value pair. value的话我们还存一个pair, value = <value, key的order> 然后key 的order 我们可以选择存一个pointer 指向这个在"list" 里面的key. 这样的话, 一来每次往list加新东西的时候, 我们存的pointer 指向的key 是不会变的. 二来把pointer 存在hash map 里面,可以保证了O(1). 这样每次put 一个existing key 的时候, 更新"list"里的order 就可以直接从hash map 的value 这里 通过先前存好的pointer找到对应在"list" 里的key, 然后可以直接删除和更新.
因为"list" 是自带iterator 的 ADT, 所以我就用iterator 代替pointer 存在hash map 里了.
写了一大堆。 上code. 应该看完这么多解释可以理解code了
class LRUCache {
int capacity;
list<int> order;
unordered_map<int, pair<int, list<int>::iterator>> cache;
public:
LRUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
if(cache.find(key) == cache.end()){
return -1;
} else {
int val = cache[key].first;
this->order.erase(cache[key].second);
this->order.push_front(key);
this->cache.at(key).second = this->order.begin(); //每一次存begin 是因为我门
//每一次都加到最前面, 而这个最前面是一个link list的node. 指向他的iterator
//会一直指向这个node,不会因为下一次增加了新的node 而指向新的begin的node。
// 意思就是这里的begin 只代表当前的begin 不是永远指向 order这个list的begin。
return val;
}
}
void put(int key, int value) {
if(this->cache.find(key) == this->cache.end()){
if(this->cache.size() == this->capacity){
int back = this->order.back();
this->order.pop_back();
this->cache.erase(back);
}
this->order.push_front(key);
this->cache[key] = {value, this->order.begin()};
} else {
this->order.erase(cache[key].second);
this->order.push_front(key);
this->cache[key] = {value, this->order.begin()};
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/