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)。

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

相关推荐
时空无限8 小时前
vllm 缓存对模型启动时间的影响
缓存·vllm
青山木12 小时前
Redis 缓存一致性全解:从 Cache Aside 到延迟双删、Canal+MQ 的进阶之路
java·数据库·redis·后端·缓存
咏方舟【长江支流】14 小时前
【开源】跨语言·跨平台·跨数据库(6) ——一种ORM缓存的接口实现和容错处理
数据库·缓存·开源·咏方舟-长江支流·用宝框架
爬也要爬着前进1 天前
redis主从搭建
数据库·redis·缓存
明豆1 天前
Redis 分布式缓存生产实战
redis·分布式·缓存
熊猫钓鱼>_>2 天前
Redis 突发缓存穿透:一次完整的定位复盘
数据库·人工智能·redis·缓存·ai·agent·智能
~kiss~2 天前
大模型的缓存
缓存
刘小八3 天前
Redis 缓存一致性:一文讲透延时双删原理、并发时序
java·数据库·redis·缓存
数行拙笔3 天前
Redis---zset类型
数据库·redis·缓存
足球中国3 天前
nuget把缓存搬离C盘
c语言·windows·缓存