导言
今天的算法打卡题是一道困难题,毫无悬念,没有做出来,可是仍是记录一下自己的思考过程,以及需求去学习和掌握的常识点
标题描绘
过程
剖析
在看完标题描绘之后,我接着便是好好研究了一下它提供的示例:
看完之后,有了大约的思路:
- 需求两个目标,
LFUCacheItem
目标表明每个缓存项;LFUCache
目标则是用来保护一切缓存项的容器 -
LFUCacheItem
目标包含的特点:- key:键
- value:值
- usageCount:缓存项运用次数计数器
- lastAccessed: 最终一次访问缓存项的时刻
-
LFUCache
目标包含的特点:- capacity:缓存的最大容量
- cache:一个字典,用于将键映射到对应缓存项(LFUCacheItem目标)
-
LFUCache
目标包含的办法:- get:获取缓存中存在的缓存项的值;缓存项存在回来值,不存在回来 -1
- put:向缓存中参加缓存项;缓存项存在更新值,不存在创立新值参加
- 超出缓存容量需求
evict
最不常运用的缓存项
- 超出缓存容量需求
- evict:当缓存满了之后,删去最不常运用的缓存项
- 判别标准:usageCount + lastAccessed
- 当
usageCount
相同的情况下,依据lastAccessed
决定运用频率
使用
依据上面对标题的剖析之后,完成的代码如下,一比一复刻,便是加了一些关于边界条件的判别:
var LFUCache = function(capacity) {
this.capacity = capacity;
this.cache = new Map();
};
var LFUCacheItem = function(key, value, usageCount = 1) {
this.key = key;
this.value = value;
this.usageCount = usageCount;
this.lastAccessed = performance.now();
}
LFUCache.prototype.get = function(key) {
if(this.cache.has(key)) {
const cacheItem = this.cache.get(key);
cacheItem.usageCount += 1;
cacheItem.lastAccessed = performance.now();
return cacheItem.value;
}
return -1;
};
LFUCache.prototype.put = function(key, value) {
if(this.capacity === 0) return;
if(this.cache.has(key)) {
const cacheItem = this.cache.get(key);
cacheItem.usageCount += 1;
cacheItem.lastAccessed = performance.now();
cacheItem.value = value;
return;
}
if(this.cache.size >= this.capacity) {
this.evict();
}
this.cache.set(key, new LFUCacheItem(key, value));
};
LFUCache.prototype.evict = function() {
const minFrequency = new LFUCacheItem(0, 0, Infinity);
this.cache.forEach((cacheItem) => {
if(cacheItem.usageCount < minFrequency.usageCount) {
minFrequency.key = cacheItem.key;
minFrequency.usageCount = cacheItem.usageCount;
minFrequency.lastAccessed = cacheItem.lastAccessed;
} else if(cacheItem.usageCount == minFrequency.usageCount) {
if(cacheItem.lastAccessed < minFrequency.lastAccessed) {
minFrequency.key = cacheItem.key;
minFrequency.usageCount = cacheItem.usageCount;
minFrequency.lastAccessed = cacheItem.lastAccessed;
}
}
})
this.cache.delete(minFrequency.key);
}
成果
在提交后,发现最终几个测试用例超出时刻约束了,然后回来标题描绘检查,的确,有说到get
和put
办法的平均时刻复杂度要为O(1)
回来检查代码会发现,get
办法是没有问题的,主要是put
办法,在参加缓存项时需求依据缓存容量和当前缓存size
来决定是否需求evict
不经常运用的缓存项,而我之前完成evict
办法时是经过遍历一切缓存项并依据evict
标准去删去不经常运用的缓存项,这归于暴力处理的办法,时刻复杂度是O(n)
,不符合标题要求,因此会超时
再剖析
依据上面成果已经推测出是evict
办法的问题,要使evict
删去不经常用的缓存项的时刻复杂度为O(1)
,那便是不需求for
循环遍历这步操作,要是缓存目标LFUCache
本身就实时保护了minFrequency
变量,用来实时保护运用频率最低的缓存项,那么在evict
办法里便能够直接删去即可
现在问题便是:如何完成实时保护minFrequency
变量?
羞愧,我在尝试了一番后,仍是没能完成,实在是超出能力范围了,看了一下题解,有说到的办法:
- 哈希表
- 双向链表
- 红黑树
- 单调栈
- ……
结论
关于LFU 缓存
这个打卡题,目前我是打不成功了,记录下来思路与需求去了解的常识,在掌握这些常识的某一天,再次来打卡~