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 小时前
力扣:146.LRU 缓存
算法·leetcode·缓存
空中海4 小时前
第四篇:进阶篇 — 缓存、消息队列、安全与常用中间件
安全·缓存·中间件
人道领域6 小时前
【黑马点评日记】Redis分布式锁终极方案:Redisson全面解析(含源码解析)
java·数据库·redis·分布式·缓存
BullSmall6 小时前
Redis AOF 文件损坏报错:完整修复方案
数据库·redis·缓存
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.1 天前
《redis-cluster 集群部署完全手册(含扩容+缩容)》
数据库·redis·缓存
八秒记忆的老男孩1 天前
Sentinel5P的L1B级数据预处理(BD7和BD8)【20260427】
数据库·redis·缓存
snow@li1 天前
数据库-Oracle:常用语法 / Oracle 核心知识技能梳理
数据库·redis·缓存
星辰_mya1 天前
系统里的“特种部队”——缓存
缓存
snow@li1 天前
数据库-Redis:常用语法 / Redis 核心知识技能梳理
数据库·redis·缓存
fuquxiaoguang1 天前
金蝶天燕AMDC:当企业级缓存遇见Redis 8.2,国产中间件的“性能+易用”双飞跃
redis·缓存·中间件