最近趁着暑假刷点leetcode找点算法的感觉,按顺序依次向下刷,结果到这题就没前期那么顺利,卡了两三天。题目大意就是模拟cache内容替换的LRU算法,整个过程十分简单,难点主要在于题目本身对算法时间的严格限制。
首先应该很容易想到利用list(增删效率高)模拟cache,然后用map(查找key速度快)来形成键值对的映射。简单的数据结构选择搞定了之后就是纠结的实现过程:
1、set、get均为O(n)
我首先敲了一个set、get方法均为O(n)的想要速战速决(list存储key值,map做映射,这样每次set要使用erase方法擦掉对应key并将其添加到尾部,每次get需要find到相应的key然后再输出map[key],这里都需要遍历一遍list),结果TLE。分析一下,问题主要出在find和remove方法底层都得遍历一遍list,因此得想办法将其复杂度优化到O(1),很自然就会又祭起map这等神器。因此就过度到了如下的方法。
2、set、get均为O(1)
在1的基础上再次增加一个map容器,记录每次key值和cache块变更的迭代器位置的映射(好奇葩的思路,当时还纠结了一下能不能这么映射,结果还真可以,顿时空中飘来五只可爱的草泥马。。),这样每次需要删除和查找时就能通过key值直接定位到迭代器,因此能压缩时间复杂度。需要注意的是迭代器cache.end()需要自减后再记录,另外友情提醒一下list顺序删除时需要将erase(it)的返回值重新赋值到it以避免list断链(与本题无关)。
总结一下,设计算法首先要有一个大致的框架,确定好基本的数据结构,然后从差的复杂度逐步将其优化,这种逐步求精的过程非常令人享受。另外找时间需要总结一下stl各类容器的基本实现和方法运用,便于自己查阅和复习。附上C++代码和测试用例纪念一下回归后的第一帖
#include <iostream>
#include <map>
#include <list>
using namespace std;
class LRUCache{
int ca;
map<int, int> mp;
map<int, list<int>::iterator> loc;
list<int> cache;
public:
LRUCache(int capacity) {
ca = capacity;
mp.clear();
loc.clear();
cache.clear();
}
int get(int key) {
if (mp[key] == 0)return -1;
if (mp[key] == -2)return 0;//若输入value为-2,则为map中有value=0的数值插入
if (mp[key] != -1){
cache.erase(loc[key]);
cache.push_back(key);
loc[key] = --(cache.end());
}
return mp[key];
}
void set(int key, int value) {
if (mp[key] == -1 || mp[key] == 0){//如果当前key值不存在
if (cache.size() == ca){//若cache已满,弹出最少使用的块(list头)
mp[*(cache.begin())] = -1;
cache.pop_front();
}
}
else{//若key存在,删除后再插入到list尾部
cache.erase(loc[key]);
}
cache.push_back(key);
loc[key] = --(cache.end());
if (value == 0){
mp[key] = -2;
}
else
mp[key] = value;
}
};
int main()
{
LRUCache lch(2);
char a;
while (cin >> a){
if (a == 's'){
int key, value;
cin >> key >> value;
lch.set(key, value);
}
else {
int key;
cin >> key;
cout<<"get answer:"<<lch.get(key)<<endl;;
}
}
return 0;
}
/*
s 1 11
s 2 22
g 1
s 3 33
g 2
g 3
*/