[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 thecapacity
of the data structure.int get(int key)
Gets the value of thekey
if thekey
exists in the cache. Otherwise, returns-1
.void put(int key, int value)
Update the value of thekey
if present, or inserts thekey
if not already present. When the cache reaches itscapacity
, 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 usedkey
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 toget
andput
.
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 */
相关题目