LeetCode题目
LeetCode题目
LFU算法
LFU算法是什么
昨天,我们手写了一个LRU算法,而LFU算法也是一种缓存淘汰算法。
LFU算法全名Least Frequently Used ,最近最少使用。LFU算法的设计思路
LFU算法满足以下几点:1、get方法返回key对应的val;2、只要使用get或者put方法访问一次key对应的元素,该元素的频次freq+1;3、如果容量满时进行put,则需要将freq最小的key删除,如果最小的freq对应多个key,则删除其中最旧的那一个
基于LFU算法的要求,我们使用多个基础数据结构进行设计:1、使用HashMap存储key和val,满足O(1)复杂度的key-val查询;2、使用一个HashMap存储key和freq,满足使用get或put访问一个key,则让freq+1;3、需要freq到key的映射,用来找到freq对应最小的key,同时可能有多个key拥有相同的freq,所以freq对key是一对多的关系,即一个freq对应一个key的列表,同时,key列表存在时序的,便于快速查找并删除最旧的key,使用HashMap+LinkedHashSet;4、将freq最小的key删除,快速得到当前所有key最小的freq是多少。想要O(1),就用一个变量minFreq来记录当前最小的freq,根据该freq瞬间得到对应的val列表
LFU的基本数据结构
class LFU { // key 到 val 的映射 HashMap<Integer, Integer> keyToVal; // key 到 freq 的映射 HashMap<Integer, Integer> keyToFreq; // freq 到 key 列表的映射 HashMap<Integer, LinkedHashSet<Integer>> freqToKeys; // 记录最小的频次 int minFreq; // 记录 LFU 缓存的最大容量 int cap; public LFUCache(int capacity) { keyToVal = new HashMap<>(); keyToFreq = new HashMap<>(); freqToKeys = new HashMap<>(); this.cap = capacity; this.minFreq = 0; } public int get(int key) { // 没有key,返回-1 if (!keyToVal.containsKey(key)) { return -1; } // 有,则让freq+1,然后返回val increaseFreq(key); return keyToVal.get(key); } public void put(int key, int val) { if (keyToVal.containsKey(key)) { // key存在,则修改key对应的val,并且让freq+1 keyToVal.put(key, val); increaseFreq(key); } else { // key不存在,则添加元素。添加前判断容量 if (this.cap <= keyToVal.size()) { // 容量不足,删除元素 removeMinFreqKey(); } // 直接插入 keyToVal.put(key, val); keyToFreq.put(key, 1); freqToKeys.putIfAbsent(1, new LinkedHashSet<>()); // 因为插入后,freq为1,freq最少为1,因此还要添加freq为1的key对应的key列表 freqToKeys.get(1).add(key); // 设置最小freq为1 this.minFreq = 1; } } /**LFU算法核心**/ /** * 更新key对应的freq */ private void increaseFreq(int key) { // 获取当前key对应的当前freq int freq = keyToFreq.get(key); // 更新key-freq映射 keyToFreq.put(key, freq + 1); // 更新freq-key映射 // 将 key 从 freq 对应的列表中删除 freqToKeys.get(freq).remove(key); // 将key放入freq+1的对应val列表中 freqToKeys.putIfAbsent(freq + 1, new LinkedHashSet<>()); freqToKeys.get(freq + 1).add(key); // 此时,如果freq对应的val列表为空,则移除该freq if (freqToKeys.get(freq).isEmpty()) { freqToKeys.remove(freq); // 如果这个 freq 恰好是 minFreq,更新 minFreq if (freq == this.minFreq) { this.minFreq++; } } } /** * 删除元素 */ private void removeMinFreqKey() { // 获取最小的freq对应的key列表 LinkedHashSet<Integer> keyList = freqToKeys.get(this.minFreq); // 列表表头的元素,就是应该删除的key。获取该key Integer deleteKey = keyList.iterator().next(); // 删除列表中对应的该key keyList.remove(deleteKey); if (keyList.isEmpty()) { // 如果删除后该列表为空,则应该清除该freq-key列表的映射关系 freqToKeys.remove(this.minFreq); } // 删除key-val映射中的key keyToVal.remove(deleteKey); // 删除key-freq映射中的key keyToFreq.remove(deleteKey); }; }