LeetCode之LFU缓存机制
- 请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
- 实现 LFUCache 类:
class LFUCache {
//用数据结构的容量 capacity 初始化对象
public LFUCache(int capacity) {
}
//如果键存在于缓存中,则获取键的值,否则返回 -1。
public int get(int key) {
}
// 如果键已存在,则变更其值;如果键不存在,请插入键值对。
// 当缓存达到其容量时,则应该在插入新项之前,删除 最不经常使用的项。
// 在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。
// 注意「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。
// 为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器。
//当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。
public void put(int key, int value) {
}
}
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache obj = new LFUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
-
需求:
- 调用get方法,可以根据 键key 快速 获取value,调用put方法,可以将 键值对 存储起来
- 之后每次调用get或put方法,键值对的 使用次数 应该+1
- 调用put方法时,如果发现容量已经达到了上限,那么首先会删除那个 使用次数最少 的项;如果有多个 使用次数最少 的项,那么删除其中 最久未使用 的项。
-
分析:
- 为了满足需求1,使用
HashMap<Integer, Integer> key2Val
来保存键值对 - 为了满足需求2,使用
HashMap<Integer, Integer> key2Freq
来保存键及其使用次数 - 为了满足需求3中,快速找到 最少的使用次数,设置
int minFreq
来保存 最少的使用次数 - 为了能够快速找到使用次数最少的项,使用
HashMap<Integer, key的列表> freq2Keys
来保存 使用次数到键 的映射,另外为了能够在 多个使用次数最少的项中 删除最久未使用的项,那么希望key的列表是有序的。所以,或许可以使用LinkedList
来保存 key的列表。但是我们还有一个需求,在使用了某个key之后,需要快速在 key的列表 中找到这个key,并从 这个key的列表 中删除,加入使用次数+1的 key的列表,所以,我们需要使用LinkedHashSet
来保存key的列表。 LinkedHashSet
是链表和哈希集合的结合体。链表不能快速访问链表节点,但是插入元素具有时序;哈希集合中的元素无序,但是可以对元素进行快速的访问和删除。那么,它俩结合起来就兼具了哈希集合和链表的特性,既可以在 O(1) 时间内访问或删除其中的元素,又可以保持插入的时序。
- 为了满足需求1,使用
-
代码实现
class LFUCache {
private HashMap<Integer, Integer> key2Val;
private HashMap<Integer, Integer> key2Freq;
private HashMap<Integer, LinkedHashSet<Integer>> freq2Keys;
private int capacity;
private int minFreq;
public LFUCache(int capacity) {
this.key2Val = new HashMap<>();
this.key2Freq = new HashMap<>();
this.freq2Keys = new HashMap<>();
this.capacity = capacity;
this.minFreq = 1;
}
public int get(int key) {
boolean containsKey = key2Val.containsKey(key);
if(containsKey){
int val = key2Val.get(key);
//增加key的使用次数
increaseFreq(key);
return val;
}
else{
return -1;
}
}
public void put(int key, int value) {
if(this.capacity<=0){
return;
}
boolean containsKey = key2Val.containsKey(key);
//存在key
if(containsKey){
//修改键值对
key2Val.put(key,value);
//增加key的使用次数
increaseFreq(key);
}
else {
//达到了 容量上限
if(key2Val.size()==capacity){
//删除最少使用的键值对
deleteMinFreq();
}
//加入新节点
//更新kv
key2Val.put(key,value);
//更新kf
key2Freq.put(key,1);
//更新fk
LinkedHashSet<Integer> orDefault = freq2Keys.getOrDefault(1, null);
if(orDefault==null){
LinkedHashSet<Integer> set = new LinkedHashSet<>();
set.add(key);
freq2Keys.put(1,set);
}
else {
orDefault.add(key);
}
//更新minFreq
minFreq = 1;
}
}
private void deleteMinFreq(){
//更新fk
LinkedHashSet<Integer> keys = freq2Keys.get(this.minFreq);
int deleteKey = keys.iterator().next();//linkedHashSet中第一个元素就是最先加入的元素
keys.remove(deleteKey);
if(keys.isEmpty()){
freq2Keys.remove(this.minFreq);
}
//更新kv
key2Val.remove(deleteKey);
//更新kf
key2Freq.remove(deleteKey);
}
private void increaseFreq(int key){
int freq = key2Freq.get(key);
//更新kf
key2Freq.put(key,freq+1);
//更新fk
LinkedHashSet<Integer> keys = freq2Keys.get(freq);
keys.remove(key);
if (keys.isEmpty()){
freq2Keys.remove(freq);
//更新minKey
if(this.minFreq == freq){
this.minFreq++;
}
}
LinkedHashSet<Integer> orDefault = freq2Keys.getOrDefault(freq + 1, null);
if(orDefault==null){
LinkedHashSet<Integer> set = new LinkedHashSet<>();
set.add(key);
freq2Keys.put(freq+1,set);
}
else {
orDefault.add(key);
}
}
}