题目描述
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.get(3); // returns 3.
cache.put(4, 4); // evicts key 1.
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
题目分析
主要是页面替换算法中的LFU算法,在一定时期内最少使用的替换掉,与LRU不同的,LFU为每个页面都设置了使用频率,当缓存中容量满的时候,插入新的页面,就从原来的缓存选择一个使用频率最少的页面给替换掉,如果有多哥页面的使用频率相同,那么就选择最早放入缓存的那个。(LFU强调使用频率最少,LRU强调最近是否使用过即可)
例子分析
举个例子来看看,比如说我们的cache的大小为3,然后我们按顺序存入 5,4,5,4,5,7,这时候cache刚好被装满了,因为put进去之前存在的数不会占用额外地方。那么此时我们想再put进去一个8,如果使用LRU算法,应该将4删除,因为4最久未被使用,而如果使用LFU算法,则应该删除7,因为7被使用的次数最少,只使用了一次。LRU只需使用一个Hashmap加一个双向循环链表即可解决, 最近使用的元素放在链表头,最近最少使用的元素放在聊表尾巴。
LFU思路
使用一个HashMap记录(key,value),再使用一个HashMap记录key, frequently,另一个LinkedHashSet记录元素插入
的顺须,设置三个变量,cap记录缓存的大小,val记录缓存中的(key,value), count-(key, 频率),List 记录(频率,key(列表)),key列表中最早加入的元素是在表头的,尾巴是新加入,同一个key<列表中>存放的是使用频率一样的元素,当val.size()==cap的时候,就设法从最小的key<列表中>查找一个key最小元素,然后删除最新加入的即可。
- 什么时候需要更新min
无论什么元素,只要是第一次添加进来的元素,则min一定是等于1的
如果某个min列表中只有一个元素,并且访问的是min中的元素,则min就要进行更新操作,
import java.util.HashMap;
import java.util.LinkedHashSet;
public class LFUCache {
private int cap;
private int min=-1;
private HashMap<Integer,Integer> value;
private HashMap<Integer,Integer> frequently;
private HashMap<Integer,LinkedHashSet<Integer>> frequently_list;
LFUCache(int capacity) {
cap = capacity;
value = new HashMap<>();
frequently = new HashMap<>();
frequently_list =new HashMap<>();
//此时并没有包含新的元素,只是创建了新的而已
frequently_list.put(1,new LinkedHashSet<>());
}
private void free_space(){
//空间足够
if(value.size()<cap)
return;
//空间不足够要从min(key)链表中取出一个元素并删除
int key =(int) frequently_list.get(min).iterator().next();
System.out.println("evict "+ key);
/* 三个变量均要删除该变量, */
value.remove(key);
frequently.remove(key);
frequently_list.get(min).remove(key);
}
public int get(int key) {
/* 如果不包含的话 */
if(!value.containsKey(key)) return -1;
//如果包含的话,访问一次需要更新一次访问次数
update(key);
/* update frequently,and frequently_list;两个东西 */
return value.get(key);
}
private void update(int key){
int frequency = frequently.get(key);
//覆盖操作即可
frequently.put(key,frequency+1);
//从原来的频率列表中删除,
frequently_list.get(frequency).remove(key);
//放进入新的元素列表,如果还没存在相应的频率列表,则应该新创建出来
if(!frequently_list.containsKey(frequency+1))
frequently_list.put(frequency+1,new LinkedHashSet<Integer>());
frequently_list.get(frequency+1).add(key);
if(frequently_list.get(frequency).size()==0 && frequency==min)
++min;
}
public void put(int key, int value_) {
if(value.containsKey(key))
{
//执行更新value
value.put(key,value_);
//更新frequently 和frequently_list;,
update(key);
return;
}
//如果是第一次出现的
//先确保又足够的空间
free_space();
value.put(key,value_);
frequently.put(key,1);
//第一次出现的情况,即使在这里
frequently_list.get(1).add(key);
min = 1;
}
public static void main(String[] args) {
LFUCache cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1)); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
System.out.println(cache.get(3));
cache.put(4, 4); // evicts key 1.
System.out.println(cache.get(1));
System.out.println(cache.get(3));
System.out.println(cache.get(4));
}
}