leetcode 460. LFU缓存
题目详情
题目链接
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get 和 put。
- get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
- put(key, value) - 如果键不存在,请设置或插入值。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近 最少使用的键。
「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。
- 进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?- 示例:
LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
我的代码
class LFUCache {
public:
list<pair<int, int>> useTimes;
map<int, int> contents;
int size;
int cur;
LFUCache(int capacity) {
size = capacity;
cur = 0;
}
inline void dealExist(int key) {
pair<int, int> find;
int findLoc = 0;
bool isFind = false;
for (auto loc = useTimes.begin(); loc != useTimes.end(); ++loc) {
if (loc->first == key) {
find = make_pair(key, loc->second + 1);
useTimes.erase(loc);
break;
}
}
for (auto l = useTimes.begin(); l != useTimes.end(); ++l) {
if (l->second > find.second) {
useTimes.insert(l, find);
isFind = true;
break;
}
}
if (!isFind) {
useTimes.emplace_back(find);
}
}
inline void insertFirst(int key) {
bool isFind = false;
for (auto l = useTimes.begin(); l != useTimes.end(); ++l) {
if (l->second > 1) {
useTimes.insert(l, make_pair(key, 1));
isFind = true;
break;
}
}
if (!isFind) {
useTimes.emplace_back(make_pair(key, 1));
}
}
int get(int key) {
if (size == 0) {
return -1;
}
if (contents.count(key) == 0) {
return -1;
}
dealExist(key);
return contents[key];
}
void put(int key, int value) {
if (size == 0) {
return;
}
if (cur < size) {
if (contents.count(key) == 0) {
insertFirst(key);
++cur;
} else {
dealExist(key);
}
} else {
if (contents.count(key) == 0) {
contents.erase(useTimes.begin()->first);
useTimes.erase(useTimes.begin());
insertFirst(key);
} else {
dealExist(key);
}
}
contents[key] = value;
}
};
我的成绩
执行结果:通过
执行用时 : 1408 ms, 在所有 C++ 提交中击败了5.39%的用户
内存消耗 : 39.7 MB, 在所有 C++ 提交中击败了40.00%的用户
一些想法
- 本道题是一道困难题,但题目并不是很难,只是非常繁琐。
- 本题我的想法是用两个数据结构来记录内容,一个用 list<pair<int, int>> useTimes来记录key和使用次数,还有就是用 map<int, int> contents 来记录要保存的key和alue,然后在get和put时做相应更新即可。
执行用时为 80 ms 的范例
class LFUCache {
class Bucket;
class Key {
public:
int key, val;
Bucket *pfreq;
Key *prev, *next;
Key(int k, int v, Bucket* f) : key(k), val(v), pfreq(f) {}
Key(Key* p, Key* n) : prev(p), next(n) {}
};
class Bucket {
Key _head = Key(&_head, &_head);
Key *head = &_head;
public:
int refc, sz = 0;
Bucket() {}
bool empty() {
return sz == 0;
}
void removeKey(Key* pkey) {
pkey->prev->next = pkey->next;
pkey->next->prev = pkey->prev;
--sz;
}
void insertKey(Key* pkey) {
pkey->next = head->next;
pkey->prev = head;
head->next->prev = pkey;
head->next = pkey;
++sz;
}
int popLast() {
Key *rm = head->prev;
int key = rm->key;
removeKey(rm);
delete rm;
return key;
}
};
unordered_map<int, Bucket> blist;
unordered_map<int, Key*> cache;
int cap;
Bucket* minf = NULL, *pzero = &blist[0];
void add(int key, int v) {
minf = pzero;
Key* newKey = new Key(key, v, pzero);
pzero->insertKey(newKey);
cache.insert(make_pair(key, newKey));
}
int& val(int key) {
Key* entry = cache[key];
Bucket *before = entry->pfreq, *after = &blist[before->refc + 1];
after->refc = before->refc + 1;
entry->pfreq = after;
before->removeKey(entry);
after->insertKey(entry);
if(before->empty() && before == minf)
minf = after;
if(before->refc && before->empty())
blist.erase(before->refc);
return entry->val;
}
void pop() {
int outkey = minf->popLast();
cache.erase(outkey);
if(minf->refc && minf->empty())
blist.erase(minf->refc);
}
public:
LFUCache(int capacity) {
cap = capacity;
pzero->refc = 0;
}
int get(int key) {
if(!cap || !cache.count(key))
return -1;
return val(key);
}
void put(int key, int value) {
if(cap == 0) return;
if(!cache.count(key)) {
if(cache.size() == cap)
pop();
add(key, value);
}
else {
val(key) = value;
}
}
};
思考
本道题推荐看官方讲解。