每日一道算法題——LRU緩存機制

點擊查看原題——146. LRU緩存機制

運用你所掌握的數(shù)據(jù)結(jié)構(gòu)构舟,設(shè)計和實現(xiàn)一個 LRU (最近最少使用) 緩存機制宇整。它應(yīng)該支持以下操作: 獲取數(shù)據(jù) get 和 寫入數(shù)據(jù) put 妙同。

獲取數(shù)據(jù) get(key) - 如果密鑰 (key) 存在于緩存中,則獲取密鑰的值(總是正數(shù))场靴,否則返回 -1队丝。
寫入數(shù)據(jù) put(key, value) - 如果密鑰不存在玩祟,則寫入其數(shù)據(jù)值腹缩。當(dāng)緩存容量達(dá)到上限時,它應(yīng)該在寫入新數(shù)據(jù)之前刪除最近最少使用的數(shù)據(jù)值空扎,從而為新的數(shù)據(jù)值留出空間藏鹊。

進(jìn)階:

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

LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );

cache.put(1, 1); 
//cache [(1,1)]
cache.put(2, 2);
//cache[(2,2),(1,1)]
cache.get(1);       // 返回  1
//cache[(1,1),(2,2)]
cache.put(3, 3);    // 該操作會使得密鑰 2 作廢
//cache[(3,3),(1,1)]
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 該操作會使得密鑰 1 作廢
//cache [(4,4),(3,3)]
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
// cache[(3,3),(4,4)]
cache.get(4);       // 返回  4
//cache[(4,4),(3,3)]

分析

LRU算法實際上是:最近操作的數(shù)據(jù)位于數(shù)據(jù)結(jié)構(gòu)的隊首转锈。并且put和get操作的時間復(fù)雜度均是O(1)盘寡。

實際上這個Cache的特點:查找快、刪除快撮慨、插入快竿痰、有順序。

  1. HashMap除了無序砌溺,其他3個特點均滿足影涉;
  2. 鏈表是有序的,并且刪除和插入的時間復(fù)雜度均為O(1)规伐。雖然HashMap可以快速定位到Node節(jié)點常潮,但單鏈表中只保存了一個指針,無法進(jìn)行刪除操作楷力。所以使用雙向鏈表,即使定位到一個節(jié)點孵户,也可以確定其前節(jié)點和后節(jié)點萧朝。
LRU的核心思想.png

故最終的存儲結(jié)構(gòu)是哈希表與雙向鏈表的結(jié)合

  • 哈希表定位Node節(jié)點:解決鏈表的查找緩慢問題夏哭;
  • 雙向鏈表存儲Value:解決哈希表的無序問題检柬;

哈希表存儲key,用于定位到鏈表匯總的value。那么Node中為什么還要存儲key呢何址?

因為Node自我刪除的時候里逆,要去Map中刪除對應(yīng)的key。但若是Node只保存Value用爪,沒有保存key原押。找不到對應(yīng)的Map數(shù)據(jù)。即Node節(jié)點也需要映射map偎血。

代碼實現(xiàn)

1. 創(chuàng)建Node節(jié)點诸衔。

public class LRUCache {
    static class Node {

        //鏈表數(shù)據(jù)域為key和val
        private int key, val;
        private Node next, prev;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
}

2. 創(chuàng)建雙向鏈表,并實現(xiàn)其中的API方法

public class LRUCache {
    static class DoubleList {
        private Node head, tail;  //頭節(jié)點颇玷,尾節(jié)點
        private int size;
        //構(gòu)建雙向鏈表
        public DoubleList() {
            this.head = new Node(0, 0);
            this.tail = new Node(0, 0);
            head.next = tail;
            tail.prev = head;
            this.size = 0;
        }
        //鏈表頭部插入數(shù)據(jù)
        public void addFirst(Node node) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
            size++;
        }
        //刪除鏈表中的node節(jié)點
        public void remove(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
            size--;
        }
        //刪除鏈表的最后一個節(jié)點,并返回該節(jié)點
        public Node removeLast() {
            if (tail.prev == head) {
                return null;
            }
            //保存尾指針
            Node lastNode = tail.prev;
            tail.prev.prev.next = tail;
            tail.prev = tail.prev.prev;
            size--;
            return lastNode;
        }
        //返回鏈表長度
        public int size() {
            return size;
        }
    }
}

3. 實現(xiàn)LRU緩存

public class LRUCache {
    //記錄 節(jié)點的位置
    HashMap<Integer, Node> map = new HashMap<>(16);
    //真正的緩存
    DoubleList cache = new DoubleList();
    //最大容量
    private int cap;
    public LRUCache(int capacity) {
        cap = capacity;
    }
    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        int val = map.get(key).val;
        //使用put方法將數(shù)據(jù)提前,先刪后頭插
        put(key, val);
        return val;
    }
    public void put(int key, int value) {
        //先new出新節(jié)點
        Node node = new Node(key, value);
        //若包含節(jié)點持隧,則刪除節(jié)點后倔叼,插入到頭部
        if (map.containsKey(key)) {
            //刪除Node節(jié)點
            cache.remove(map.get(key));
            cache.addFirst(node);
            //更新map中的值
            map.put(key, node);
        } else {
            //若沒有包含頭節(jié)點
            if (cap == cache.size()) {
                //刪除鏈表的最后一個元素
                Node last = cache.removeLast();
                //注:若Node中值存儲value,那么此處不能映射到map空郊。
                map.remove(last.key);
            }
            //插入鏈表頭部
            cache.addFirst(node);
            map.put(key, node);
        }
    }
}

實際上在Java中LinkedHashMap便是使用哈希表+雙向鏈表實現(xiàn)的LRU思想份招。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渣淳,隨后出現(xiàn)的幾起案子脾还,更是在濱河造成了極大的恐慌,老刑警劉巖入愧,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鄙漏,死亡現(xiàn)場離奇詭異,居然都是意外死亡棺蛛,警方通過查閱死者的電腦和手機怔蚌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旁赊,“玉大人桦踊,你說我怎么就攤上這事≈粘” “怎么了籍胯?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長离福。 經(jīng)常有香客問我杖狼,道長蝶涩,這世上最難降的妖魔是什么嗽上? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任烹看,我火速辦了婚禮惯殊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘己儒。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布凛驮。 她就那樣靜靜地躺著宏胯,像睡著了一般扣草。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛙婴,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天尔破,我揣著相機與錄音街图,去河邊找鬼。 笑死懒构,一個胖子當(dāng)著我的面吹牛餐济,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胆剧,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼絮姆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秩霍?” 一聲冷哼從身側(cè)響起篙悯,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铃绒,沒想到半個月后鸽照,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡颠悬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年矮燎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椿疗。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡漏峰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出届榄,到底是詐尸還是另有隱情浅乔,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布铝条,位于F島的核電站靖苇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏班缰。R本人自食惡果不足惜贤壁,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望埠忘。 院中可真熱鬧脾拆,春花似錦馒索、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渠驼,卻和暖如春蜈块,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背迷扇。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工百揭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜓席。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓器一,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓮床。 傳聞我的和親對象是個殘疾皇子盹舞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

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