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

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

相关推荐
Mahir082 小时前
Spring 循环依赖深度解密:从问题本质到三级缓存源码级解析
java·后端·spring·缓存·面试·循环依赖·三级缓存
jran-7 小时前
Redis 命令
数据库·redis·缓存
189228048618 小时前
NY382固态MT29F32T08GSLBHL8-24QM:B
大数据·服务器·人工智能·科技·缓存
June`8 小时前
多线程redis下如何解决aof重写和rdb持久化的数据一致性问题
数据库·redis·缓存
Trouvaille ~10 小时前
【Redis篇】初识 Redis:特性、应用场景与版本演进
数据结构·数据库·redis·分布式·缓存·中间件·持久化
cd_9492172110 小时前
鸿蒙系统下抖音存储空间不足怎么办?缓存清理教程
缓存·华为·harmonyos
洛水水12 小时前
Redis 实现限流功能的几种方法
数据库·redis·缓存
米高梅狮子12 小时前
Redis
数据库·redis·mysql·缓存·docker·容器·github
1892280486113 小时前
NY379固态MT29F32T08GSLBHL8-36QA:B
大数据·服务器·人工智能·科技·缓存
牧羊狼的狼13 小时前
高并发会带来哪些问题,如何解决?
缓存·高并发