【算法题】LFU缓存

本文介绍了LFU(Least Frequently Used)缓存的解题思路,包括Get和Put操作的处理,并讨论了如何进行数据淘汰。提出了通过哈希表和双向链表优化数据结构,以达到O(1)的时间复杂度进行Get操作和优化Put操作时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

设计并实现最不经常使用(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操作
  1. 判断缓存中是否已存在对应的key,存在则进行访问记录数据、缓存值更新
  2. 新增缓存中未存在的值,则先判断当前缓存容量是否已经满了,如果当前缓存容量满了,则要先通过LFU进行数据淘汰操作。
  3. 插入新的缓存值

淘汰数据方法思路

  1. 遍历先存在的所有缓存
  2. 通过对比每个缓存项的访问时间,以及最后一次访问时间决定淘汰数据的键值
    (1)找出访问次数最少的CacheItem、Cache Key
    (2)如果访问次数最少的缓存键存在多个,则通过每个缓存CacheItem最后访问时间判断哪个是最久没进行访问过的,也就是最后一次访问时间是最早最小的,决定最后淘汰缓存key
  3. 将遍历筛选出来需要淘汰的缓存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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值