思路一:hash+set 复杂度O(logn) 复杂度主要来源于红黑树的插入和删除
键->值:hash
删除最久未使用:set<pair<int, int>> st; 需要删除最久未使用的键值对,所以first存键,second存出现次数。对st排序时,pair按照second递增。
class LRUCache {
public:
struct cmp{
bool operator()(pair<int, int> &p1, pair<int, int> &p2) const{
return p1.second < p2.second;
}
};
unordered_map<int, int> ump; //键值
set<pair<int, int>, cmp> st; //键;时间,按照时间升序
unordered_map<int, int> st2; //为st的拷贝,方便st删除元素
int cnt = 0;
int len;
LRUCache(int capacity) {
len = capacity;
}
int get(int key) { //随机访问键
if (ump.count(key)) {
//删除st 加入st
st.erase(make_pair(key, st2[key]));
st.insert(make_pair(key, cnt));
//修改st2
st2[key] = cnt++;
}
return ump.count(key) ? ump[key] : -1;
}
void put(int key, int value) {
if (ump.count(key)) {//存在
//删除st 加入st
st.erase(make_pair(key, st2[key]));
st.insert(make_pair(key, cnt));
//修改st2
st2[key] = cnt++;
ump[key] = value;
}else {
if (ump.size() >= len) {
// ump删除 st2删除 st删除
ump.erase(st.begin()->first);
st2.erase(st.begin()->first);
st.erase(st.begin());
}
st.insert(make_pair(key, cnt));
st2[key] = cnt++;
ump[key] = value;
}
}
};
思路上的错误:
1.get函数对一个键获得后,它应该更改为最近访问的!
1.当put存在的键时,不应该先判断ump是否超过len然后删掉最近未使用的,应该只有当put不存在的键是才判断!
思路二:hash+list O(1)
1.键->值:hash
删除最近未使用,存在先后顺序:栈,队列,链表。然而当对一个键进行get后,要将该值的放到最近,用栈和队列的话,必须把它左边的都出去,所以栈和队列不适用。当对一个键进行get后,需要将一个结点插入到某一个节点后面,所以用双向链表,链头是最久未使用的,链尾是最近刚使用的。
2. 由于在链表中删除指定节点,复杂度达不到O(1),所以在hash中存储双向链表中结点的地址
class LRUCache {
public:
unordered_map<int, list<pair<int, int>> :: iterator> ump; //值->链表结点地址
list<pair<int, int>> lst;
//存键->值
int len;
LRUCache(int capacity) {
len = capacity;
}
int get(int key) {
if (ump.count(key)) {
lst.push_back(make_pair(key, ump[key]->second));
lst.erase(ump[key]);
ump[key] = prev(lst.end());
}
return ump.count(key) ? ump[key]->second : -1;
}
void put(int key, int value) {
if (ump.count(key)) {
lst.push_back(make_pair(key, value));
lst.erase(ump[key]);
ump[key] = prev(lst.end());
}else {
if (ump.size() >= len) {
ump.erase(lst.begin()->first);
lst.erase(lst.begin());
}
lst.push_back(make_pair(key, value));
ump[key] = prev(lst.end());
}
}
};
代码上的难点:
1.链表节点存的值:由于删除最久未访问节点时,也需要从hash中删除,所以链表中要存键,get需要获取键对应的值,所以也需要存值,也就是pair<int, int>;
2.链表结点的类型:list<pair<int, int>> :: iterator
3.erase只能删除前向迭代器,想删除最后一个元素
在双向链表中节点地址不连续,所以不能用lst.end() - 1,只能用prev(lst.end())
思路三:手写双向链表 面试会考
思路:由于需要在尾插节点,所以需要维护一个尾巴,需要删掉最久未使用,所以也需要维护一个头,搞一个有头有尾的双链表
class LRUCache {
public:
struct node {
int k;
int v;
node *before;
node *after;
node(int _k, int _v) : k(_k), v(_v) {}
node(int _k, int _v, node *_before, node *_after) :
k(_k), v(_v), before(_before), after(_after){}
};
unordered_map<int, node*> ump;
int len;
node *head, *tail;
LRUCache(int capacity) {
len = capacity;
head = new node(-1, -1);
tail = new node(-2, -2);
head->after = tail;
tail->before = head;
}
int get(int key) {
if (ump.count(key)) {
move(key);
}
return ump.count(key) ? ump[key]->v : -1;
}
void put(int key, int value) {
if (ump.count(key)) {
move(key);
ump[key]->v = value;
}else {
node *tmp;
if (ump.size() >= len) {
tmp = head->after;
head->after = tmp->after;
tmp->after->before = head;
ump.erase(tmp->k);
delete tmp;
}
ump[key] = new node(key, value, tail->before, tail);
ump[key]->before->after = ump[key];
tail->before = ump[key];
}
}
void move(int key) { //将key对应的节点移到最近访问的位置(tail前面)
ump[key]->after->before = ump[key]->before;
ump[key]->before->after = ump[key]->after;
node* tmp = tail->before;
ump[key]->after = tail;
ump[key]->before = tmp;
tail->before = ump[key];
tmp->after = ump[key];
}
};
简化代码:双链表可以写这样一个构造函数:
node(int _k, int _v, node *_before, node *_after)
同时指向前后节点