[LeetCode] 460. LFU Cache

Design and implement a data structure for a Least Frequently Used (LFU) cache.

Implement the LFUCache class:

  • LFUCache(int capacity) Initializes the object with the capacity of the data structure.
  • int get(int key) Gets the value of the key if the key exists in the cache. Otherwise, returns -1.
  • void put(int key, int value) Update the value of the key if present, or inserts the key if not already present. When the cache reaches its capacity, it should invalidate and remove the least frequently used key before inserting a new item. For this problem, when there is a tie (i.e., two or more keys with the same frequency), the least recently used key would be invalidated.

To determine the least frequently used key, a use counter is maintained for each key in the cache. The key with the smallest use counter is the least frequently used key.

When a key is first inserted into the cache, its use counter is set to 1 (due to the put operation). The use counter for a key in the cache is incremented either a get or put operation is called on it.

The functions get and put must each run in O(1) average time complexity.

Example 1:

Input
["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]]
Output
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]

Explanation
// cnt(x) = the use counter for key x
// cache=[] will show the last used order for tiebreakers (leftmost element is  most recent)
LFUCache lfu = new LFUCache(2);
lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
lfu.get(1);      // return 1
                 // cache=[1,2], cnt(2)=1, cnt(1)=2
lfu.put(3, 3);   // 2 is the LFU key because cnt(2)=1 is the smallest, invalidate 2.
                 // cache=[3,1], cnt(3)=1, cnt(1)=2
lfu.get(2);      // return -1 (not found)
lfu.get(3);      // return 3
                 // cache=[3,1], cnt(3)=2, cnt(1)=2
lfu.put(4, 4);   // Both 1 and 3 have the same cnt, but 1 is LRU, invalidate 1.
                 // cache=[4,3], cnt(4)=1, cnt(3)=2
lfu.get(1);      // return -1 (not found)
lfu.get(3);      // return 3
                 // cache=[3,4], cnt(4)=1, cnt(3)=3
lfu.get(4);      // return 4
                 // cache=[4,3], cnt(4)=2, cnt(3)=3

Constraints:

  • 0 <= capacity <= 104
  • 0 <= key <= 105
  • 0 <= value <= 109
  • At most 2 * 105 calls will be made to get and put.

LFU缓存。

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

实现 LFUCache 类:

LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。
void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/lfu-cache
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

首先我想对 LFU 做一些简单解释。回忆 LRU 那道题,我们实现的数据结构是当 cache 达到他的 capacity 的时候,对于已经存在于 cache 中的元素,我们不管他的出现次数,只是单纯地根据他上一次被访问的时间戳来决定他是否可以留在 cache 中,最终,时间戳最早的那个元素会被弹出。对于这道题,难点在于我们需要记录每个元素的被访问次数(频率)以及每个元素上一次被访问的时间。这样当我们超过 capacity 的时候,我们可以删除频率最低的那个元素,或者如果有多个元素频率都是最低的情况下,我们删除时间戳最早的那个元素。

这个题有两个函数需要实现,分别是 get 和 put,依然是需要在 O(1) 时间复杂度内完成。

通过这个题我学到了原来Java里面有一个叫做 LinkedHashSet 的东西。这个题既然是找 LFU,最不经常使用的缓存,所以在有 capacity 限制的情况下,使用次数最少,频率最低的元素会被淘汰。首先我给出一个图帮助理解。

如下分别解释一下两个函数的一些细节。

  • put() - 首先看是否超过 capacity,若没超过,则放入元素并 get 元素;如果超过了 capacity,需要先找到出现次数最少的那个次数所对应的 LinkedHashSet,并通过 iterator 删除这个 LinkedHashSet 中第一个被放入的元素。这个元素就是那个在 capacity 临界点需要被删除的元素。如果这个元素从未被放入过,则需要把这个元素分别放入 vals,counts 两个 hashmap 和 LinkedHashSet,并且标记最小出现次数为 1。注意 LinkedHashSet 用 iterator 读取是有序的,读取的顺序同元素被放入的顺序。
  • get() - 如果要找的元素不在缓存,则直接 return -1。如果在缓存里面,则做两件事,1 是 counts++,更新其出现次数;2 是更新这个元素在 LinkedHashSet 的位置,如果这个行为同时导致了 min 值的改变,也需要更新 min 值。

时间O(1) - required

空间O(n)

Java实现

 1 class LFUCache {
 2     HashMap<Integer, Integer> vals;                         // <key, value>
 3     HashMap<Integer, Integer> counts;                       // <key, key出现的次数>
 4     HashMap<Integer, LinkedHashSet<Integer>> list;          // <key出现的次数, 哪些元素出现了K次>
 5     int capacity;
 6     int min;
 7 
 8     public LFUCache(int capacity) {
 9         this.capacity = capacity;
10         vals = new HashMap<>();
11         counts = new HashMap<>();
12         list = new HashMap<>();
13         list.put(1, new LinkedHashSet<>());
14         min = -1;
15     }
16 
17     public int get(int key) {
18         // corner case
19         if (!vals.containsKey(key)) {
20             return -1;
21         }
22         // normal case
23         int count = counts.get(key);
24         counts.put(key, count + 1);
25         list.get(count).remove(key);
26         if (count == min && list.get(count).size() == 0) {
27             min++;
28         }
29         if (!list.containsKey(count + 1)) {
30             list.put(count + 1, new LinkedHashSet<>());
31         }
32         list.get(count + 1).add(key);
33         return vals.get(key);
34     }
35 
36     public void put(int key, int value) {
37         // corner case
38         if (capacity <= 0) {
39             return;
40         }
41         // normal case
42         if (vals.containsKey(key)) {
43             vals.put(key, value);
44             get(key);
45             return;
46         }
47         // if it's over the capacity
48         if (vals.size() >= capacity) {
49             int evit = list.get(min).iterator().next();
50             list.get(min).remove(evit);
51             vals.remove(evit);
52         }
53         // if this is never saved
54         vals.put(key, value);
55         counts.put(key, 1);
56         min = 1;
57         list.get(1).add(key);
58     }
59 }
60 
61 /**
62  * Your LFUCache object will be instantiated and called as such:
63  * LFUCache obj = new LFUCache(capacity);
64  * int param_1 = obj.get(key);
65  * obj.put(key,value);
66  */

 

相关题目

146. LRU Cache

460. LFU Cache

LeetCode 题目总结

posted @ 2020-04-06 09:01  CNoodle  阅读(249)  评论(0编辑  收藏  举报