146. LRU Cache
Title
運(yùn)用你所掌握的數(shù)據(jù)結(jié)構(gòu),設(shè)計(jì)和實(shí)現(xiàn)一個(gè) LRU (最近最少使用) 緩存機(jī)制。它應(yīng)該支持以下操作: 獲取數(shù)據(jù) get 和 寫入數(shù)據(jù) put 。
獲取數(shù)據(jù) get(key) -:如果密鑰 (key) 存在于緩存中,則獲取密鑰的值(總是正數(shù)),否則返回 -1。
寫入數(shù)據(jù) put(key, value) - 如果密鑰已經(jīng)存在,則變更其數(shù)據(jù)值;如果密鑰不存在,則插入該組「密鑰/數(shù)據(jù)值」。當(dāng)緩存容量達(dá)到上限時(shí),它應(yīng)該在寫入新數(shù)據(jù)之前刪除最久未使用的數(shù)據(jù)值,從而為新的數(shù)據(jù)值留出空間。
進(jìn)階:
你是否可以在 O(1) 時(shí)間復(fù)雜度內(nèi)完成這兩種操作?
示例:
LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );cache.put(1, 1); cache.put(2, 2); cache.get(1); // 返回 1 cache.put(3, 3); // 該操作會(huì)使得密鑰 2 作廢 cache.get(2); // 返回 -1 (未找到) cache.put(4, 4); // 該操作會(huì)使得密鑰 1 作廢 cache.get(1); // 返回 -1 (未找到) cache.get(3); // 返回 3 cache.get(4); // 返回 4Solve
自帶數(shù)據(jù)結(jié)構(gòu):
這尼瑪好熟悉啊,Python里不是有一種結(jié)合了哈希表和雙鏈表的數(shù)據(jù)結(jié)構(gòu)叫OrderedDict么,幾行代碼就能解決戰(zhàn)斗。
class LRUCache(collections.OrderedDict):def __init__(self, capacity: int):super().__init__()self.capacity = capacitydef get(self, key: int) -> int:if key not in self:return -1self.move_to_end(key=key)return self[key]def put(self, key: int, value: int) -> None:if key in self:self.move_to_end(key=key)self[key] = valueif len(self) > self.capacity:self.popitem(last=False)但顯然在面試的時(shí)候這不是面試官想要的結(jié)果,因此我們還是用哈希表+雙鏈表維護(hù)一個(gè)數(shù)據(jù)結(jié)構(gòu)吧。
哈希表+雙鏈表:
LRU 緩存機(jī)制可以通過(guò)哈希表輔以雙鏈表維護(hù)所有在緩存中的鍵值對(duì)。
- 雙鏈表按照被使用的順序存儲(chǔ)鍵值對(duì),靠近頭部的鍵值對(duì)是最近被使用的,靠近尾部的鍵值對(duì)是最久沒(méi)被使用過(guò)的。
- 哈希表即為普通的哈希映射(HashMap),通過(guò)緩存數(shù)據(jù)的鍵映射到其在雙鏈表中的位置。
這樣以來(lái),首先使用哈希表進(jìn)行定位,找出緩存項(xiàng)在雙鏈表中的位置,隨后將其移動(dòng)到雙鏈表的頭部,即可在O(1)的時(shí)間內(nèi)完成get或者put操作。
具體的方法如下:
-
對(duì)于get操作,首先判斷key是否存在:
- 如果key不存在,返回-1;
- 如果key存在,則key對(duì)應(yīng)的節(jié)點(diǎn)是最近被使用的節(jié)點(diǎn),通過(guò)哈希表定位到該節(jié)點(diǎn)在雙鏈表中的位置并將其移動(dòng)到雙鏈表的頭部,最后返回該節(jié)點(diǎn)的值。
-
對(duì)于post操作,首先判斷key是否存在:
- 如果key不存在,使用key和value創(chuàng)建一個(gè)新的節(jié)點(diǎn),在雙鏈表的頭部添加該節(jié)點(diǎn),并將key和該節(jié)點(diǎn)添加到哈希表中,然后判斷雙鏈表的節(jié)點(diǎn)數(shù)是否超出容量:
- 如果超出容量,刪除雙鏈表的尾部節(jié)點(diǎn),并刪除哈希表中對(duì)應(yīng)的項(xiàng)
- 如果key存在,則與get操作類似,先通過(guò)哈希表定位,再將對(duì)應(yīng)的節(jié)點(diǎn)的值更新為value,并將該節(jié)點(diǎn)移動(dòng)到雙鏈表的頭部。
- 如果key不存在,使用key和value創(chuàng)建一個(gè)新的節(jié)點(diǎn),在雙鏈表的頭部添加該節(jié)點(diǎn),并將key和該節(jié)點(diǎn)添加到哈希表中,然后判斷雙鏈表的節(jié)點(diǎn)數(shù)是否超出容量:
上述各項(xiàng)操作中,訪問(wèn)哈希表的時(shí)間復(fù)雜度為 O(1),在雙向鏈表的頭部添加節(jié)點(diǎn)、在雙向鏈表的尾部刪除節(jié)點(diǎn)的復(fù)雜度也為 O(1)。而將一個(gè)節(jié)點(diǎn)移到雙向鏈表的頭部,可以分成「刪除該節(jié)點(diǎn)」和「在雙向鏈表的頭部添加節(jié)點(diǎn)」兩步操作,都可以在 O(1) 時(shí)間內(nèi)完成。
小貼士
在雙向鏈表的實(shí)現(xiàn)中,使用一個(gè)偽頭部(dummy head)和偽尾部(dummy tail)標(biāo)記界限,這樣在添加節(jié)點(diǎn)和刪除節(jié)點(diǎn)的時(shí)候就不需要檢查相鄰的節(jié)點(diǎn)是否存在。
復(fù)雜度分析
時(shí)間復(fù)雜度:對(duì)于 put 和 get 都是 O(1)。
空間復(fù)雜度:O(capacity),因?yàn)楣1砗碗p向鏈表最多存儲(chǔ) capacity+1 個(gè)元素。
Code
class DoubleLinkNode:def __init__(self, key=0, value=0):self.key, self.value, self.prev, self.next = key, value, None, Noneclass LRUCache:def __init__(self, capacity: int):self.cache, self.capacity, self.size = dict(), capacity, 0self.head, self.tail = DoubleLinkNode(), DoubleLinkNode()self.head.next, self.tail.prev = self.tail, self.headdef get(self, key: int) -> int:if key not in self.cache:return -1# 如果 key 存在,先通過(guò)哈希表定位,再移到頭部node = self.cache[key]self.moveToHead(node)return node.valuedef put(self, key: int, value: int) -> None:if key not in self.cache:# 如果 key 不存在,創(chuàng)建一個(gè)新的節(jié)點(diǎn)node = DoubleLinkNode(key, value)# 添加進(jìn)哈希表self.cache[key] = node# 添加至雙向鏈表的頭部self.addToHead(node)self.size += 1if self.size > self.capacity:# 如果超出容量,刪除雙向鏈表的尾部節(jié)點(diǎn)removed = self.removeTail()# 刪除哈希表中對(duì)應(yīng)的項(xiàng)self.cache.pop(removed.key)self.size -= 1else:# 如果 key 存在,先通過(guò)哈希表定位,再修改 value,并移到頭部node = self.cache[key]node.value = valueself.moveToHead(node)def addToHead(self, node):node.prev = self.headnode.next = self.head.nextself.head.next.prev = nodeself.head.next = nodedef removeNode(self, node):node.prev.next = node.nextnode.next.prev = node.prevdef moveToHead(self, node):self.removeNode(node)self.addToHead(node)def removeTail(self):node = self.tail.prevself.removeNode(node)return node總結(jié)
以上是生活随笔為你收集整理的146. LRU Cache的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 4. Median of Two Sor
- 下一篇: 287. Find the Duplic