題目
設計並實現最不經常使用(LFU)緩存的數據結構。它應該支持以下操作:get 和 put。
get(key) - 如果鍵存在於緩存中,則獲取鍵的值(總是正數),否則返回 -1。
put(key, value) - 如果鍵不存在,請設置或插入值。當緩存達到其容量時,它應該在插入新項目之前,使最不經常使用的項目無效。在此問題中,當存在平局(即兩個或更多個鍵具有相同使用頻率)時,最近最少使用的鍵將被去除。
進階:
你是否可以在 O(1) 時間複雜度內執行兩項操作?
示例:
LFUCache cache = new LFUCache( 2 /* capacity (緩存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/lfu-cache
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
分析
開始覺得可以加入一個有序字典,但是後來實現的時候還是不夠用。
且看官方題解吧,自己的編程能力還是太水了:
方法二:雙哈希表
思路和算法
我們定義兩個哈希表,第一個 freq_table 以頻率 freq 爲索引,每個索引存放一個雙向鏈表,這個鏈表裏存放所有使用頻率爲 freq 的緩存,緩存裏存放三個信息,分別爲鍵 key,值 value,以及使用頻率 freq。第二個 key_table 以鍵值 key 爲索引,每個索引存放對應緩存在 freq_table 中鏈表裏的內存地址,這樣我們就能利用兩個哈希表來使得兩個操作的時間複雜度均爲 O(1)O(1)。同時需要記錄一個當前緩存最少使用的頻率 minFreq,這是爲了刪除操作服務的。
對於 get(key) 操作,我們能通過索引 key 在 key_table 中找到緩存在 freq_table 中的鏈表的內存地址,如果不存在直接返回 -1,否則我們能獲取到對應緩存的相關信息,這樣我們就能知道緩存的鍵值還有使用頻率,直接返回 key 對應的值即可。
但是我們注意到 get 操作後這個緩存的使用頻率加一了,所以我們需要更新緩存在哈希表 freq_table 中的位置。已知這個緩存的鍵 key,值 value,以及使用頻率 freq,那麼該緩存應該存放到 freq_table 中 freq + 1 索引下的鏈表中。所以我們在當前鏈表中 O(1)O(1) 刪除該緩存對應的節點,根據情況更新 minFreq 值,然後將其O(1)O(1) 插入到 freq + 1 索引下的鏈表頭完成更新。這其中的操作複雜度均爲 O(1)O(1)。你可能會疑惑更新的時候爲什麼是插入到鏈表頭,這其實是爲了保證緩存在當前鏈表中從鏈表頭到鏈表尾的插入時間是有序的,爲下面的刪除操作服務。
對於 put(key, value) 操作,我們先通過索引 key在 key_table 中查看是否有對應的緩存,如果有的話,其實操作等價於 get(key) 操作,唯一的區別就是我們需要將當前的緩存裏的值更新爲 value。如果沒有的話,相當於是新加入的緩存,如果緩存已經到達容量,需要先刪除最近最少使用的緩存,再進行插入。
先考慮插入,由於是新插入的,所以緩存的使用頻率一定是 1,所以我們將緩存的信息插入到 freq_table 中 1 索引下的列表頭即可,同時更新 key_table[key] 的信息,以及更新 minFreq = 1。
那麼剩下的就是刪除操作了,由於我們實時維護了 minFreq,所以我們能夠知道 freq_table 裏目前最少使用頻率的索引,同時因爲我們保證了鏈表中從鏈表頭到鏈表尾的插入時間是有序的,所以 freq_table[minFreq] 的鏈表中鏈表尾的節點即爲使用頻率最小且插入時間最早的節點,我們刪除它同時根據情況更新 minFreq ,整個時間複雜度均爲 O(1)O(1)。
作者:LeetCode-Solution
鏈接:https://leetcode-cn.com/problems/lfu-cache/solution/lfuhuan-cun-by-leetcode-solution/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
class Node:
def __init__(self, key, val, pre=None, nex=None, freq=0):
self.pre = pre
self.nex = nex
self.freq = freq
self.val = val
self.key = key
def insert(self, nex): # 雙向鏈表的插入節點操作
nex.pre = self
nex.nex = self.nex
self.nex.pre = nex
self.nex = nex
def create_linked_list(): # 空鏈表
head = Node(0, 0)
tail = Node(0, 0)
head.nex = tail
tail.pre = head
return (head, tail)
class LFUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.size = 0
self.minFreq = 0
self.freqMap = collections.defaultdict(create_linked_list)
self.keyMap = {}
def delete(self, node):
if node.pre:
node.pre.nex = node.nex
node.nex.pre = node.pre
if node.pre is self.freqMap[node.freq][0] and node.nex is self.freqMap[node.freq][-1]:
self.freqMap.pop(node.freq)
return node.key
def increase(self, node):
node.freq += 1
self.delete(node)
self.freqMap[node.freq][-1].pre.insert(node)
if node.freq == 1:
self.minFreq = 1
elif self.minFreq == node.freq - 1:
# 如果這個新調用的node freq值原來是最小freq值,那麼就要看他原來所在的雙鏈表中
#除了head tail結點外是否還有節點,如果有就不動,如果沒有,那麼minfreq也要+1了
head, tail = self.freqMap[node.freq - 1]
if head.nex is tail:
self.minFreq = node.freq
def get(self, key: int) -> int:
if key in self.keyMap:
self.increase(self.keyMap[key])
return self.keyMap[key].val
return -1
def put(self, key: int, value: int) -> None:
if self.capacity != 0:
if key in self.keyMap:
node = self.keyMap[key]
node.val = value
else:
node = Node(key, value)
self.keyMap[key] = node
self.size += 1
if self.size > self.capacity:
self.size -= 1
deleted = self.delete(self.freqMap[self.minFreq][0].nex)
self.keyMap.pop(deleted)
self.increase(node)
作者:LeetCode-Solution
鏈接:https://leetcode-cn.com/problems/lfu-cache/solution/lfuhuan-cun-by-leetcode-solution/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
這裏面的思考過程對我來說還是比較複雜的,日後一定要來重寫