Leetcode 460. LFU Cache

📅2023-07-18🧐77

Don't afraid about the hard label on this problem. If you could understand LRU cache (https://freeyeti.net/leetcode-146-lru-cache/) this question would be easy.

Simplely, after reading the description we could know LFU cache is just LRU cache with a counter, so in this problem our focus is to design a data structure that can save counter along the LRU. Intuitively, we know hashmap could get value in a O(1) time complexity, so we just use it.

let's take a look on my draft for this problem. And usually this is how I start to think about a question, just use the text editor or IDE directly:

0  "LFUCache"  [2]    null
1  "put"       [1,1]  null  
2  "put"       [2,2]  null  
3  "get"       [1]    1
4  "put"       [3,3]  null  
5  "get"       [2]    -1
6  "get"       [3]    3
7  "put"       [4,4]  null  
8  "get"       [1]    -1
9  "get"       [3]    3
10  "get"       [4]    4

step1:
freq[1] = {1:1}
counter[1] = 1 # counter[key] saves how many times this key has been used, the value of counter[key] is the key of freq, so we can faster navigate to that OrderedDict

step2:
freq[1] = {1:1, 2:2}
counter[1] = 1
counter[2] = 1

step3:
freq[1] = {2:2}
freq[2] = {1:1}
counter[1] = 2
counter[2] = 1

step4:
freq[1] = {3:3} # we pop 2 since it's the least recent one
freq[2] = {1:1}
counter[1] = 2
counter[2] = 0
counter[3] = 1

step 6:
freq[1] = {}
freq[2] = {1:1, 3:3} 
# since OrderedDict always add new item to the most right, don't worry the order
counter[1] = 2
counter[2] = 0
counter[3] = 2

step 7:
freq[1] = {4:4}
freq[2] = {3:3} # we pop 1
counter[1] = 0
counter[2] = 0
counter[3] = 2
counter[4] = 1

Let me try to draw the idea:

algo-lfu-cache

See, the solution is pretty simple, just use LRU cache with a hashmap to save the count of useage like here:

from collections import defaultdict, OrderedDict

class LFUCache:
    def __init__(self, capacity: int):
        self.cache = {}
        self.counter = defaultdict(int)
        self.freq = defaultdict(OrderedDict)
        self.min = 0
        self.cap = capacity

    def update(self, key):
        count = self.counter[key]
        self.counter[key] += 1
        if key in self.freq[count]:
            self.freq[count].move_to_end(key)
            self.freq[count].popitem()
        self.freq[count + 1][key] = key

        if count == self.min and len(self.freq[count]) == 0:
            self.min = count + 1

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1

        self.update(key)
        return self.cache[key]
        
    def put(self, key: int, value: int) -> None:
        if self.cap == 0:
            return

        if key not in self.cache and self.cap == len(self.cache):
            res, _ = self.freq[self.min].popitem(False)
            del self.cache[res]
            self.counter[res] = 0

        self.cache[key] = value
        self.update(key)
        self.min = min(self.min, self.counter[key])

# Your LFUCache object will be instantiated and called as such:
# obj = LFUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)