460. LFU 缓存

LFU缓存(O(1)实现)

LFU的淘汰规则:访问次数最少的先淘汰,次数相同则淘汰最久没用的。

核心问题:怎么做到O(1)?

如果每次淘汰都遍历所有key找最小频次,是O(n),太慢。

关键在于维护一个 minFreq 变量,随时知道当前最小频次是多少,淘汰时直接定位,不用遍历。

三个数据结构:

keyToVal :key → value

keyToFreq :key → 访问次数

freqToKeys :访问次数 → 该次数下所有key(按访问时间排序)

freqToKeys 的value用 LinkedHashSet,原因是:

同频次下要淘汰最久没用的,LinkedHashSet按插入顺序排,头部就是最旧的

还能O(1)删除任意元素

minFreq怎么维护?

只有两种情况需要更新:

put新key时 → 新key频次必为1,所以 minFreq = 1

某个key频次+1,旧桶变空,且旧桶就是minFreq → minFreq++

其他情况不用动,这是整个方案的精髓。

完整代码:

java 复制代码
class LFUCache {
    int cap, minFreq;
    Map<Integer, Integer> keyToVal;
    Map<Integer, Integer> keyToFreq;
    Map<Integer, LinkedHashSet<Integer>> freqToKeys;

    public LFUCache(int capacity) {
        this.cap = capacity;
        keyToVal = new HashMap<>();
        keyToFreq = new HashMap<>();
        freqToKeys = new HashMap<>();
    }

    public int get(int key) {
        if (!keyToVal.containsKey(key)) return -1;
        increaseFreq(key);
        return keyToVal.get(key);
    }

    public void put(int key, int val) {
        if (keyToVal.containsKey(key)) {
            keyToVal.put(key, val);
            increaseFreq(key);
            return;
        }
        if (keyToVal.size() >= cap) {
            removeMinFreq();
        }
        keyToVal.put(key, val);
        keyToFreq.put(key, 1);
        freqToKeys.computeIfAbsent(1, k -> new LinkedHashSet<>()).add(key);
        minFreq = 1;
    }

    // key的频次+1,更新三个map
    private void increaseFreq(int key) {
        int freq = keyToFreq.get(key);
        keyToFreq.put(key, freq + 1);
        freqToKeys.get(freq).remove(key);
        if (freqToKeys.get(freq).isEmpty()) {
            freqToKeys.remove(freq);
            if (minFreq == freq) minFreq++;
        }
        freqToKeys.computeIfAbsent(freq + 1, k -> new LinkedHashSet<>()).add(key);
    }
    //等同于下方freqToKeys.computeIfAbsent
/**if (!freqToKeys.containsKey(freq + 1)) {
    freqToKeys.put(freq + 1, new LinkedHashSet<>());
}
freqToKeys.get(freq + 1).add(key);*/
    // 淘汰minFreq桶里最旧的key
    private void removeMinFreq() {
        LinkedHashSet<Integer> keys = freqToKeys.get(minFreq);
        int evictKey = keys.iterator().next();
        keys.remove(evictKey);
        if (keys.isEmpty()) freqToKeys.remove(minFreq);
        keyToVal.remove(evictKey);
        keyToFreq.remove(evictKey);
    }
}

一句话总结:

用 minFreq 快速定位最小频次,用 LinkedHashSet 在同频次内实现LRU兜底,两者结合实现全程O(1)。

难度等级:⭐️⭐️⭐️⭐️⭐️⭐️再加半颗

相关推荐
禾小西4 小时前
05丨Redis 内存快照:宕机后,如何实现快速恢复?
数据库·redis·缓存
李纲明6 小时前
WordPress 站点变慢:按 TTFB → 缓存 → 查询 → 前端 分层排查(含配置注意)
前端·缓存·性能优化·wordpress·后端开发
我不会插花弄玉7 小时前
4.string类型【由浅入深-redis】
数据库·redis·缓存
Riwuarua7 小时前
从近似0基础开始FPGA开发——Part.23:AXI/AXIS 数据搬运、DMA 与 DDR 缓存架构设计
缓存·fpga开发
姜鱼问生21 小时前
Nginx 缓存命中率监控:从 X-Cache-Status 到实时统计
运维·nginx·缓存
weixin199701080161 天前
《1688图片空间API踩坑:img.upload 与 album.* 的防盗链与CDN缓存问题》(附Python源码)
开发语言·python·缓存
hweiyu001 天前
Redis命令:BLPOP
redis·缓存
小小龙学IT1 天前
C++ Redis 客户端深度解析(hiredis 与 redis-plus-plus)——工业数采实时缓存 C++ 侧实战
c++·redis·缓存
mengge.cloud1 天前
Redis
运维·服务器·数据库·redis·缓存·云计算
欣栀♡1 天前
Redis 缓存穿透、击穿、雪崩,背出定义只是及格线
数据库·redis·缓存