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,就是最不经常使用页置换算法。难点:
一:O(1)访问速度,所以要使用hash,形式是<key,val>
二:更新访问频率,而且也要O(1),所以也要使用hash,形式是<key,count>
三:无多余空间时,删除访问频率最低的元素,所以对于每个count也要记录下,形式是<count,linkededhashset>,因为linkedhashset有序,可以对应当访问次数相同时按先后次序剔除(即删除链表开头元素)
四:存在<key,old> -> <key,new>这种情况,所以除了更近=新键值对以外,也要讲访问频率更新
原理都明白,可真正写的时候真的是困难!!!
public class LFUCache {
int min = -1;// 表示当前最小的访问频率等级
HashMap<Integer, Integer> val = new HashMap<Integer, Integer>();// key,val
HashMap<Integer, Integer> counts = new HashMap<Integer, Integer>();// key,counts
HashMap<Integer, LinkedHashSet<Integer>> level = new HashMap<Integer, LinkedHashSet<Integer>>();// count,set
// 记录每个访问频率等级集下的key
int size = 0;
public LFUCache(int capacity) {
this.size = capacity;
level.put(1, new LinkedHashSet<Integer>());
min = 1;//最小值的作用是为了当空间不足时,找到访问频率最低的那个集中最靠前(出现最早)的元素而设置
}
public int get(int key) {
if (!val.containsKey(key)) {// key不存在,返回-1
// System.out.println("key不存在");
return -1;
}
int count = (counts.get(key) == null) ? 0 : counts.get(key);// 记录频率
// System.out.println(count);
int result = val.get(key);// 获取返回值
counts.put(key, count + 1);// 记录下新访问频率
level.get(count).remove(key);// 剔除原访问等级下,包含的该key
if (!level.containsKey(count + 1)) {// 如果将要更新的访问等级还为未出现过,创建一个新的访问等级集
level.put((count + 1), new LinkedHashSet<Integer>());
}
level.get((count + 1)).add(key);// 更新访问等级集中的key
if (min == count && level.get(count).size() == 0)// 修改最小值
{
min++;
}
// System.out.println("get "+key+" :"+val);
return result;
}
public void put(int key, int value) {
if (size <= 0)
return;// 没有空间
if (val.containsKey(key)) {// 仅仅是更新值,包括空间未满与空间刚刚号重慢两种情况,仅需要更新val值与访问次数(相当于一次get)
val.put(key, value);
get(key);
return;
}
if ((val.size() >= size))// 空间不足,先移除最近访问次数最低的元素
{
// System.out.println(min);
int oldkey = level.get(min).iterator().next();
// System.out.println("oldkey:"+oldkey);
val.remove(oldkey);
counts.remove(oldkey);
level.get(min).remove(oldkey);
// System.out.println("after remove ,val:"+val);
// System.out.println("after remove ,count:"+counts);
}
// 添加新元素,此时或者一开始就有剩余空间,或者因为空间已满而清理出空间后有剩余空间
val.put(key, value);
counts.put(key, 1);
level.get(1).add(key);
min = 1;
// System.out.println("put "+key+" :"+val);
}
public static void main(String[] args){
StringBuilder sb = new StringBuilder();
//["LFUCache","put","put","get","put","get","get","put","get","get","get"]
//[[2],[1,1],[2,2],[1],[3,3],[2],[3],[4,4],[1],[3],[4]]
/*
LFUCache obj = new LFUCache(2);
obj.put(1,1);
obj.put(2,2);
sb.append(obj.get(1));
obj.put(3,3);
sb.append(obj.get(2));
sb.append(obj.get(3));
obj.put(4,4);
sb.append(obj.get(1));
sb.append(obj.get(3));
sb.append(obj.get(4));
System.out.println(sb.toString());
*/
LFUCache obj = new LFUCache(0);
obj.put(0,0);
sb.append(obj.get(0));
System.out.println(sb.toString());
}
}