LeetCode - 146.LRU Cache

常見的緩存機制

LRU Cache

Least Recently Used鸵荠,在操作系統(tǒng)里是一種常用的頁面置換算法,置換策略即伤极,選擇最近最久未使用的頁面予以淘汰

LeetCode - 146.LRU Cache

link: https://leetcode-cn.com/problems/lru-cache/

中文題目

運用你所掌握的數(shù)據(jù)結(jié)構(gòu)腰鬼,設(shè)計和實現(xiàn)一個 LRU (最近最少使用) 緩存機制 。
實現(xiàn) LRUCache 類:

LRUCache(int capacity) 以正整數(shù)作為容量 capacity 初始化 LRU 緩存
int get(int key) 如果關(guān)鍵字 key 存在于緩存中塑荒,則返回關(guān)鍵字的值熄赡,否則返回 -1 。
void put(int key, int value) 如果關(guān)鍵字已經(jīng)存在齿税,則變更其數(shù)據(jù)值彼硫;如果關(guān)鍵字不存在,則插入該組「關(guān)鍵字-值」凌箕。當(dāng)緩存容量達(dá)到上限時拧篮,它應(yīng)該在寫入新數(shù)據(jù)之前刪除最久未使用的數(shù)據(jù)值,從而為新的數(shù)據(jù)值留出空間牵舱。

進(jìn)階:你是否可以在 O(1) 時間復(fù)雜度內(nèi)完成這兩種操作串绩?

Example

輸入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
輸出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解釋
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 緩存是 {1=1}
lRUCache.put(2, 2); // 緩存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 該操作會使得關(guān)鍵字 2 作廢,緩存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 該操作會使得關(guān)鍵字 1 作廢芜壁,緩存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

Note:

  1. 1 <= capacity <= 3000
  2. 0 <= key <= 3000
  3. 0 <= value <= 104
  4. 最多調(diào)用 3 * 104 次 get 和 put

問題描述

需要實現(xiàn)一個LRU緩存礁凡,需要保證時間復(fù)雜度是O(1)

題目分析

數(shù)據(jù)結(jié)構(gòu)分析

  • 根據(jù)LRU Cache的定義高氮,實際的替換原則就是將最久未使用過的元素置換出去
  • 既然涉及到key和value,需要快速在O(1)時間內(nèi)快速查找數(shù)據(jù)顷牌,那就得用到hash數(shù)據(jù)結(jié)構(gòu)
  • 需要將最新使用的數(shù)據(jù)放到最前剪芍,最久未使用的數(shù)據(jù)放后面,這就是個鏈表結(jié)構(gòu)
  • 當(dāng)數(shù)據(jù)容量滿了的時候窟蓝,需要將最久未使用的數(shù)據(jù)罪裹,也就是鏈表最后一個數(shù)據(jù)刪掉,那這就需要用到雙端鏈表运挫,且需要支持雙向查找状共,因此節(jié)點的數(shù)據(jù)結(jié)構(gòu)需要選用帶有雙端的雙向鏈表
  • 為此,我們可以創(chuàng)建一個偽head和偽tail谁帕,用于指向第一個和最后一個節(jié)點
  • 為了get操作在O(1)時間內(nèi)完成口芍,我們可以在hash里直接對節(jié)點進(jìn)行索引,這樣就能根據(jù)key快速定位節(jié)點在鏈表中的位置及前后關(guān)系

操作過程分析

  • 對于get操作
    • 如果key在dict中雇卷,那我們需要將節(jié)點從原本位置移動到第一個節(jié)點處鬓椭,也就是head的指向
    • 如果key不在dict中,那我們直接按題意返回-1即可
  • 對于put操作
    • 如果key在dict中关划,那么我們同get時一樣小染,直接移動到第一個節(jié)點處即可,此時需要注意的是贮折,value值可能被覆蓋裤翩,所以對節(jié)點的value需要重新賦值
    • 如果key不在dict中,那就需要進(jìn)行添加操作了调榄,當(dāng)然這個節(jié)點是最近剛使用的踊赠,那么一定是在第一個節(jié)點位置
    • 當(dāng)新的節(jié)點被添加進(jìn)來時,由于cache是有容量限制的每庆,因此需要將最久未使用的那個數(shù)據(jù)移除掉筐带,也就是最后一個節(jié)點相關(guān)數(shù)據(jù)
  • 其它
    • 為了調(diào)試,我寫了個visit方法缤灵,這個方法可以遍歷鏈表伦籍,輸出中間結(jié)果

代碼實現(xiàn)

Python3 實現(xiàn)

class Node(object):
    def __init__(self, key = 0, value = 0):
        self.key = key
        self.value = value
        self.next = None
        self.prev = None


class LRUCache:
    def visit(self):
        result = []
        p = self.head.next
        while p and p != self.tail:
            result.append(str(p.value))
            p = p.next
        # print('->'.join(result))

    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache = {}
        self.head = Node()
        self.tail = Node()
        self.head.next = self.tail
        self.tail.prev = self.head

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        node = self.cache[key]
        self.move_node_to_head(node)
        # self.visit()
        return node.value

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            node = self.cache[key]
            node.value = value
            self.move_node_to_head(node)
        else:
            node = Node(key, value)
            self.add_to_head(node)
            self.cache[key] = node
            if len(self.cache) <= self.capacity:
                pass
            else:
                last_node = self.remove_tail_node()
                self.cache.pop(last_node.key)
        # self.visit()

    def remove_node(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def add_to_head(self, node):
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node

    def move_node_to_head(self, node):
        self.remove_node(node)
        self.add_to_head(node)

    def remove_tail_node(self):
        node = self.tail.prev
        node.prev.next = self.tail
        self.tail.prev = node.prev
        return node
            

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

運行結(jié)果

Time Submitted Status Runtime Language
a few seconds ago Accepted 168 ms python3

執(zhí)行結(jié)果:

通過

顯示詳情

執(zhí)行用時:168 ms, 在所有 Python3 提交中擊敗了78.98%的用戶

內(nèi)存消耗:23.7 MB, 在所有 Python3 提交中擊敗了14.51%的用戶

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腮出,隨后出現(xiàn)的幾起案子帖鸦,更是在濱河造成了極大的恐慌,老刑警劉巖胚嘲,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件作儿,死亡現(xiàn)場離奇詭異,居然都是意外死亡馋劈,警方通過查閱死者的電腦和手機攻锰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門晾嘶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人口注,你說我怎么就攤上這事【椋” “怎么了寝志?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長策添。 經(jīng)常有香客問我材部,道長,這世上最難降的妖魔是什么唯竹? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任乐导,我火速辦了婚禮,結(jié)果婚禮上浸颓,老公的妹妹穿的比我還像新娘物臂。我一直安慰自己,他們只是感情好产上,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布棵磷。 她就那樣靜靜地躺著,像睡著了一般晋涣。 火紅的嫁衣襯著肌膚如雪仪媒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天谢鹊,我揣著相機與錄音算吩,去河邊找鬼。 笑死佃扼,一個胖子當(dāng)著我的面吹牛偎巢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兼耀,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼艘狭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翠订?” 一聲冷哼從身側(cè)響起巢音,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尽超,沒想到半個月后官撼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡似谁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年傲绣,在試婚紗的時候發(fā)現(xiàn)自己被綠了掠哥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秃诵,死狀恐怖续搀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菠净,我是刑警寧澤禁舷,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站毅往,受9級特大地震影響牵咙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜攀唯,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一洁桌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侯嘀,春花似錦另凌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溪食,卻和暖如春囊卜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背错沃。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工栅组, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枢析。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓玉掸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親醒叁。 傳聞我的和親對象是個殘疾皇子司浪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容