3
3
import java .util .HashMap ;
4
4
import java .util .LinkedHashSet ;
5
5
6
+ /**460. LFU Cache
7
+ * Design and implement a data structure for Least Frequently Used (LFU) cache.
8
+ * It should support the following operations: get and put.
9
+
10
+ get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
11
+ put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity,
12
+ it should invalidate the least frequently used item before inserting a new item.
13
+ For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency),
14
+ the least recently used key would be evicted.
15
+
16
+ Follow up:
17
+ Could you do both operations in O(1) time complexity?
18
+
19
+ Example:
20
+
21
+ LFUCache cache = new LFUCache( 2 /* capacity );
22
+
23
+ cache.put(1, 1);
24
+ cache.put(2, 2);
25
+ cache.get(1); // returns 1
26
+ cache.put(3, 3); // evicts key 2
27
+ cache.get(2); // returns -1 (not found)
28
+ cache.get(3); // returns 3.
29
+ cache.put(4, 4); // evicts key 1.
30
+ cache.get(1); // returns -1 (not found)
31
+ cache.get(3); // returns 3
32
+ cache.get(4); // returns 4
33
+
34
+ */
35
+
6
36
public class _460 {
7
37
/**
8
38
* Wikipedia: The simplest method to employ an LFU algorithm is to assign a counter to every
@@ -14,53 +44,75 @@ public class _460 {
14
44
* Policy to handle frequency ties: based on timestamp, the entries that get set into cache earlier will be evicted first.
15
45
*/
16
46
17
- class LFUCache {
47
+ public static class LFUCache {
18
48
/**credit: https://discuss.leetcode.com/topic/69737/java-o-1-very-easy-solution-using-3-hashmaps-and-linkedhashset/2*/
19
- HashMap <Integer , Integer > vals ;
20
- HashMap <Integer , Integer > counts ;
21
- HashMap <Integer , LinkedHashSet <Integer >> lists ;
49
+
50
+ HashMap <Integer , Integer > keyToValue ;/**key is the key, value is the value*/
51
+ HashMap <Integer , Integer > keyToCount ;/**key is the key, value if the count of the key/value pair*/
52
+ HashMap <Integer , LinkedHashSet <Integer >> countToLRUKeys ;
53
+ /**key is count, value is a set of keys that have the same key, but keeps insertion order*/
22
54
int cap ;
23
- int min = -1 ;
55
+ int minimumCount ;
56
+
24
57
public LFUCache (int capacity ) {
25
- cap = capacity ;
26
- vals = new HashMap <>();
27
- counts = new HashMap <>();
28
- lists = new HashMap <>();
29
- lists .put (1 , new LinkedHashSet <>());
58
+ this .minimumCount = -1 ;
59
+ this .cap = capacity ;
60
+ this .keyToValue = new HashMap <>();
61
+ this .keyToCount = new HashMap <>();
62
+ this .countToLRUKeys = new HashMap <>();
63
+ this .countToLRUKeys .put (1 , new LinkedHashSet <>());
30
64
}
31
65
32
66
public int get (int key ) {
33
- if (! vals .containsKey (key ))
67
+ if (! keyToValue .containsKey (key )) {
34
68
return -1 ;
35
- int count = counts .get (key );
36
- counts .put (key , count +1 );
37
- lists .get (count ).remove (key );
38
- if (count ==min && lists .get (count ).size ()==0 )
39
- min ++;
40
- if (!lists .containsKey (count +1 ))
41
- lists .put (count +1 , new LinkedHashSet <>());
42
- lists .get (count +1 ).add (key );
43
- return vals .get (key );
69
+ }
70
+ int count = keyToCount .get (key );
71
+ keyToCount .put (key , count + 1 );
72
+ countToLRUKeys .get (count ).remove (key );
73
+
74
+ if (count == minimumCount && countToLRUKeys .get (count ).size () == 0 ) {
75
+ /**This means this key's count equals to current minimumCount
76
+ * AND
77
+ * this count doesn't have any entries in the cache.
78
+ * So, we'll increment minimumCount by 1 to get the next LFU cache entry
79
+ * when we need to evict.*/
80
+ minimumCount ++;
81
+ }
82
+
83
+ if (!countToLRUKeys .containsKey (count + 1 )) {
84
+ countToLRUKeys .put (count + 1 , new LinkedHashSet <>());
85
+ }
86
+ countToLRUKeys .get (count + 1 ).add (key );
87
+
88
+ return keyToValue .get (key );
44
89
}
45
90
46
91
public void put (int key , int value ) {
47
- if (cap <= 0 )
92
+ if (cap <= 0 ) {
48
93
return ;
49
- if (vals .containsKey (key )) {
50
- vals .put (key , value );
94
+ }
95
+
96
+ if (keyToValue .containsKey (key )) {
97
+ /**If the key is already in the cache, we can simply overwrite this entry;
98
+ * then call get(key) which will do the update work.*/
99
+ keyToValue .put (key , value );
51
100
get (key );
52
101
return ;
53
102
}
54
- if (vals .size () >= cap ) {
55
- int evit = lists .get (min ).iterator ().next ();
56
- lists .get (min ).remove (evit );
57
- vals .remove (evit );
103
+
104
+ /**If the key is not in the cache, we'll check the size first, evict the LFU entry first,
105
+ * then insert this one into cache.*/
106
+ if (keyToValue .size () >= cap ) {
107
+ int evit = countToLRUKeys .get (minimumCount ).iterator ().next ();
108
+ countToLRUKeys .get (minimumCount ).remove (evit );
109
+ keyToValue .remove (evit );
58
110
}
59
- vals .put (key , value );
60
- counts .put (key , 1 );
61
- min = 1 ;
62
- lists . get ( 1 ). add ( key );
111
+ keyToValue .put (key , value );
112
+ keyToCount .put (key , 1 );
113
+ countToLRUKeys . get ( 1 ). add ( key ); /**Because we put this key/value into cache for the first time, so its count is 1*/
114
+ minimumCount = 1 ; /**For the same above reason, minimumCount is of course 1*/
63
115
}
64
116
}
65
-
117
+
66
118
}
0 commit comments