这道题的思路就是 用一个链表list来纪录最近访问key的优先级,越靠近头部的就是优先访问的,越靠近尾部的就是越久没被访问的。
询问一个key,如果不存在就返回-1,如果存在的话,因为这个key最近被访问过了,所以要在list中更新,把这个元素放到开头。
int get(int key) {
auto it = m.find(key);
if (it == m.end()) return -1;
L.splice(L.begin(), L, it->second);
return it->second->second;
}
在上面的操作中 如果要做到O(1)时间 要做到能通过key找到链表中的一个元素还要放到末尾,就需要用一个unordered_map来建立key和list迭代器的联系 而list里面可以存一个pair,也就是key对应的value,所以有了需要的数据结构
private:
int cap;
list<pair<int, int>> L;
unordered_map<int, list<pair<int, int>>::iterator> m;
在插入的时候,如果在m中能找到这个key对应的iterator,说明key对应的value是存在的,而list中也需要更新,所以通过哈希表中key对应的iterator直接删除掉这个元素,然后无论是否key对应的value存在,都需要在list的开头加入<key,value>的pair
加入以后,需要更新哈希表中key对应的iterator指向list的头部
如果加入之后 list中的个数大于capacity,那么就需要删除least recently used的那个元素,也就是list末尾的那个元素,同时在map中将该key和iterator对应的项删除
void put(int key, int value) {
auto it = m.find(key);
if (it != m.end()) L.erase(it->second);
L.push_front(make_pair(key, value));
m[key] = L.begin();
if (L.size() > cap)
{
auto it = L.rbegin();
m.erase(it->first);
L.pop_back();
}
综上 全部的代码为
class LRUCache {
private:
int cap;
list<pair<int, int>> L;
unordered_map<int, list<pair<int, int>>::iterator> m;
public:
LRUCache(int capacity) {
cap = capacity;
}
int get(int key) {
auto it = m.find(key);
if (it == m.end()) return -1;
L.splice(L.begin(), L, it->second);
return it->second->second;
}
void put(int key, int value) {
auto it = m.find(key);
if (it != m.end()) L.erase(it->second);
L.push_front(make_pair(key, value));
m[key] = L.begin();
if (L.size() > cap)
{
auto it = L.rbegin();
m.erase(it->first);
L.pop_back();
}
}
};
今天从这道题里面学习到了特别多知识点
首先 map和unorderedmap的区别是
map是红黑树 是有序的 而unordered_map是哈希散列表 是无序的,可以做到O(1)复杂度的查询
其次 C++ stl的list的遍历的写法是
for(auto it=L.begin();it!=L.end();it++)
这里面it其实是list的迭代器
还有迭代器的写法 比如说如果要写
list<pair<int,int>>L;
的迭代器 那么迭代器的声明方式是
list<pair<int,int>>::iterator
也就是哈希散列表unordered_map里面要写的参数 这里用了一个哈希散列表
前面的key表示cache里面的用来寻找value的key 而散列表中对应的value是list<pair<int,int>>的迭代器 是为了将key和list的迭代器对应起来 就能在O(1)的时间找到list中需要被删除,插到头部的元素。
然后容器的末尾不需要用L.end()–什么的来访问 因为有反向迭代器,L.rbegin()就是容器的末尾
迭代器的splice用法 L.splice(位置,哪个容器,迭代器位置) 就可以把迭代器位置对应的那个东西放到对应容器的对应位置上,对应链表复杂度应该是O(1)。
如果是L.splice(位置,哪个容器,迭代器begin,迭代器end)应该就是把[begin,end)中的元素移动到对应容器的对应位置上,很好用 下次要记住。