题目:
设计并实现最不经常使用(LFU)缓存的数据结构。它应该支持以下操作:get 和 put。
get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value) - 如果键不存在,请设置或插入值。当缓存达到其容量时,它应该在插入新项目之前,使最不经常使用的项目无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,最近最少使用的键将被去除。
进阶:
你是否可以在 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lfu-cache
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
用了当初操作系统课程设计的大概思路实现的LFU,数组存值的方式,不是最优解
- Get操作: 直接访问存储的map,map中存在对应key的CacheItem则表示缓存命中,则更新访问数据,返回结果
- Put操作
- 判断缓存中是否已存在对应的key,存在则进行访问记录数据、缓存值更新
- 新增缓存中未存在的值,则先判断当前缓存容量是否已经满了,如果当前缓存容量满了,则要先通过LFU进行数据淘汰操作。
- 插入新的缓存值
淘汰数据方法思路
- 遍历先存在的所有缓存
- 通过对比每个缓存项的访问时间,以及最后一次访问时间决定淘汰数据的键值
(1)找出访问次数最少的CacheItem、Cache Key
(2)如果访问次数最少的缓存键存在多个,则通过每个缓存CacheItem最后访问时间判断哪个是最久没进行访问过的,也就是最后一次访问时间是最早最小的,决定最后淘汰缓存key - 将遍历筛选出来需要淘汰的缓存key,从缓存map中进行delete操作,即删除淘汰缓存
说明下需要更新访问信息的两种情况
- Get操作,命中缓存 ,需要更新缓存信息
- Put操作,Put进来的Key对应缓存已存在,则需要进行缓存值以及访问信息的数据更新。
优化大概思路
通过哈希表以及双向链表维护一个插入时间有序的数据结构,因为双向链表中删除、插入一个节点的时间复杂度都是O(1),在进行数据淘汰的时候,可直接删除有序链表中的head或tail Node实现数据淘汰,降低Put操作时间复杂度。
Go解题代码:
type CacheItem struct {
Value int //保存缓存项的值
VisitTimes int //该缓存访问次数
VisitTime int //最后一次访问时间
}
type LFUCache struct {
CacheMap map[int]*CacheItem //存储缓存数据
Capacity int //缓存容量
Time int //模拟内部时间,进行有效操作的时候进行递增操作
}
func Constructor(capacity int) LFUCache {
return LFUCache{
CacheMap: make(map[int]*CacheItem, capacity),
Capacity: capacity,
Time: 0,
}
}
func (this *LFUCache) Get(key int) int {
this.Time++
if this.CacheMap[key] == nil {
return -1
} else {
//缓存命中,更新访问记录,并直接返回记录
this.CacheMap[key].VisitTime = this.Time
this.CacheMap[key].VisitTimes++
return this.CacheMap[key].Value
}
}
func (this *LFUCache) Put(key int, value int) {
if this.Capacity == 0 {
return
}
this.Time++
//命中缓存
if this.CacheMap[key] != nil {
this.CacheMap[key].VisitTimes++
this.CacheMap[key].VisitTime = this.Time
if this.CacheMap[key].Value != value {
//刷新缓存数据
this.CacheMap[key].Value = value
}
//命中缓存无需进行下续淘汰,新增操作,直接返回
return
}
if len(this.CacheMap) >= this.Capacity {
//容量满了则先进行数据淘汰
eliminateKey := -1
eliminateCache := &CacheItem{VisitTimes:math.MaxInt32, VisitTime:math.MaxInt32}
for k, v := range this.CacheMap {
if v.VisitTimes < eliminateCache.VisitTimes {
eliminateCache = v
eliminateKey = k
} else if v.VisitTimes == eliminateCache.VisitTimes && v.VisitTime < eliminateCache.VisitTime {
//淘汰访问次数相同,但最后一次访问时间更早的
eliminateCache = v
eliminateKey = k
}
}
if eliminateKey != - 1 {
delete(this.CacheMap, eliminateKey)
}
}
//新插入数据
this.CacheMap[key] = &CacheItem{
Value: value,
VisitTimes: 0,
VisitTime: this.Time,
}
}
时间复杂度
Get: O(1)
Put: O(Capacity)