0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

設(shè)計(jì)并實(shí)現(xiàn)一個(gè)滿足LRU約束的數(shù)據(jù)結(jié)構(gòu)

冬至子 ? 來源:i余數(shù) ? 作者:i余數(shù) ? 2023-06-07 17:05 ? 次閱讀

題目

請(qǐng)你設(shè)計(jì)并實(shí)現(xiàn)一個(gè)滿足 「LRU (最近最少使用) 緩存」 約束的數(shù)據(jù)結(jié)構(gòu)。

實(shí)現(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)鍵字 key 已經(jīng)存在,則變更其數(shù)據(jù)值 value ;如果不存在,

則向緩存中插入該組 key-value 。如果插入操作導(dǎo)致關(guān)鍵字?jǐn)?shù)量超過 capacity ,則應(yīng)該 「逐出」 最久未使用的關(guān)鍵字。

1.jpg

示例:

輸入
["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); // 該操作會(huì)使得關(guān)鍵字 2 作廢,緩存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 該操作會(huì)使得關(guān)鍵字 1 作廢,緩存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

提示:

1.jpg

題解(哈希表 + 雙向鏈表)

解題之前先了解一下什么是 「LRU緩存」 , LRU 的英文全稱為 Latest Recently Used,即 「最近最少使用」 。在緩存占滿的時(shí)候,先刪除最舊的數(shù)據(jù)。

1.jpg

Java 代碼實(shí)現(xiàn)

class LRUCache {

    private int capacity;

    private Map< Integer, ListNode > cache;

    // 保護(hù)節(jié)點(diǎn),protectHead.next 為head節(jié)點(diǎn), protectTail.pre為tail節(jié)點(diǎn)
    private ListNode protectHead = new ListNode();
    private ListNode protectTail = new ListNode();

    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new HashMap<  >(capacity);
        protectHead.next = protectTail;
        protectTail.pre = protectHead;
    }


    // 刪除指定節(jié)點(diǎn)
    private void remove(ListNode listNode){
        listNode.pre.next = listNode.next;
        listNode.next.pre = listNode.pre;

        listNode.pre = null;
        listNode.next = null;
    }

    // 添加到末尾
    private void addToTail(ListNode listNode){

        protectTail.pre.next = listNode;
        listNode.pre = protectTail.pre;

        listNode.next = protectTail;
        protectTail.pre = listNode;
        
    }

    // 從當(dāng)前位置移動(dòng)到末尾
    private void moveToTail(ListNode listNode){
        
        this.remove(listNode);
        this.addToTail(listNode);
        
    }
    
    public int get(int key) {
        if(cache.containsKey(key)){
            ListNode listNode = cache.get(key);
            this.moveToTail(listNode);
            return listNode.value;
        }else{
            return -1;
        }

    }
    
    public void put(int key, int value) {
        if(cache.containsKey(key)){
            // 將 key 移動(dòng)到最新的位置
            // 1. 在舊的位置刪除
            // 2. 追加key到鏈表末尾
            ListNode listNode = cache.get(key);

            // 這里必須重新賦值,雖然緩沖已經(jīng)存在了,但是可能值不一樣。
            listNode.value = value;
            this.moveToTail(listNode);
            return;

        }

        if(cache.size() == capacity){
            // 1. 找到最舊的數(shù)據(jù),也就是鏈表的head,刪除head
            // 2. 在cache map 中刪除 head對(duì)應(yīng)的key
            ListNode headNode = protectHead.next;
            this.remove(headNode);
            cache.remove(headNode.key);
        }


        // 1. 添加新的key到cache map
        // 2. 追加新的key到鏈表末尾

        ListNode listNode = new ListNode();
        listNode.key = key;
        listNode.value = value;

        this.addToTail(listNode);
        cache.put(key, listNode);

    }
}

class ListNode{
    int key;
    int value;
    ListNode pre;
    ListNode next;

}

Go 代碼實(shí)現(xiàn)

// 定義雙向鏈表
type MyListNode struct {
    key, value int
    pre, next *MyListNode
}

type LRUCache struct { 
    size, capacity int
    cache map[int]*MyListNode
    protectHead, protectTail *MyListNode
}


func Constructor(capacity int) LRUCache {
    lruCache := LRUCache{
        size: 0,
        capacity: capacity,
        cache: map[int]*MyListNode{},
        protectHead: &MyListNode{},
        protectTail: &MyListNode{},
    }

    lruCache.protectHead.next = lruCache.protectTail
    lruCache.protectTail.pre = lruCache.protectHead
    return lruCache
}


func (this *LRUCache) Get(key int) int {
    if listNode, ok := this.cache[key]; ok {
        this.moveToTail(listNode);
        return listNode.value;
    }else{
        return -1
    }
}


func (this *LRUCache) Put(key int, value int)  {
    if listNode, ok := this.cache[key]; ok {
        listNode.value = value;
        this.moveToTail(listNode);
        return;
    }
    if(this.size == this.capacity){
        headNode := this.protectHead.next;
        this.remove(headNode);
        delete(this.cache, headNode.key);
        this.size--
    }

    listNode := &MyListNode{
        key: key,
        value: value,
    }


    this.addToTail(listNode)
    this.cache[key] = listNode
    this.size++
}

// 刪除指定節(jié)點(diǎn)
func (this *LRUCache) remove(listNode *MyListNode){
    listNode.pre.next = listNode.next;
    listNode.next.pre = listNode.pre;

    listNode.pre = nil;
    listNode.next = nil;
}


// 添加到末尾
func (this *LRUCache) addToTail(listNode *MyListNode){
    this.protectTail.pre.next = listNode
    listNode.pre = this.protectTail.pre

    listNode.next = this.protectTail
    this.protectTail.pre = listNode
}


// 從當(dāng)前位置移動(dòng)到末尾
func (this *LRUCache) moveToTail(listNode *MyListNode){
    this.remove(listNode);
    this.addToTail(listNode);
}

復(fù)雜度分析

1.jpg

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2943

    瀏覽量

    104110
  • cache技術(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    41

    瀏覽量

    1033
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    什么是數(shù)據(jù)結(jié)構(gòu)(Data Structrue)

    一個(gè)一個(gè)元素數(shù)據(jù)對(duì)象:具有相同特性的數(shù)據(jù)元素的集合結(jié)構(gòu)數(shù)據(jù)元素之間具有的關(guān)系(聯(lián)系) 二. 
    發(fā)表于 02-09 17:17

    GPIB命令的數(shù)據(jù)結(jié)構(gòu)

    GPIB命令結(jié)點(diǎn);考慮程序實(shí)現(xiàn)的效率問題以及管理維護(hù)方面的因素,對(duì)普通的樹進(jìn)行改造,從而形成特有的"GPIB命令樹"?!娟P(guān)鍵詞】:通用接口總線(GPIB);;數(shù)據(jù)結(jié)構(gòu);;樹
    發(fā)表于 04-24 09:44

    【原創(chuàng)】Android開發(fā)—Lru核心數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)突破緩存框架

    【原創(chuàng)】Android開發(fā)—Lru核心數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)突破緩存框架回復(fù)即可獲取下載鏈接[hide=d15]鏈接:http://pan.baidu.com/s/1c2BfjsW 密碼:bta5 更多學(xué)習(xí)資料加Q:1352716312,
    發(fā)表于 06-21 16:58

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

    數(shù)據(jù)的邏輯結(jié)構(gòu)分為線性結(jié)構(gòu)和非線性結(jié)構(gòu)。個(gè)非空的線性結(jié)構(gòu)
    發(fā)表于 03-04 14:13

    常見的數(shù)據(jù)結(jié)構(gòu)

    順序表結(jié)構(gòu)的底層實(shí)現(xiàn)借助的就是數(shù)組,因此對(duì)于初學(xué)者來說,可以把順序表完全等價(jià)為數(shù)組,但實(shí)則不是這樣。數(shù)據(jù)結(jié)構(gòu)是研究數(shù)據(jù)存儲(chǔ)方式的門學(xué)科,它
    發(fā)表于 05-10 07:58

    GPIB命令的數(shù)據(jù)結(jié)構(gòu)

    針對(duì)GPIB命令的結(jié)構(gòu),提出種存儲(chǔ)GPIB命令的數(shù)據(jù)結(jié)構(gòu)。根據(jù)GPIB命令的層次關(guān)系的特點(diǎn),選擇數(shù)據(jù)結(jié)構(gòu)中“樹”的概念來存儲(chǔ)GPIB命令結(jié)點(diǎn);
    發(fā)表于 02-10 16:20 ?70次下載

    GPIB命令的數(shù)據(jù)結(jié)構(gòu)

    針對(duì)GPIB命令的結(jié)構(gòu),提出種存儲(chǔ)GPIB命令的數(shù)據(jù)結(jié)構(gòu)。根據(jù)GPIB命令的層次關(guān)系的特點(diǎn),選擇數(shù)據(jù)結(jié)構(gòu)中“樹”的概念來存儲(chǔ)GPIB命令結(jié)點(diǎn);
    發(fā)表于 01-04 10:13 ?0次下載

    數(shù)據(jù)結(jié)構(gòu)是什么_數(shù)據(jù)結(jié)構(gòu)有什么用

    數(shù)據(jù)結(jié)構(gòu)是計(jì)算機(jī)存儲(chǔ)、組織數(shù)據(jù)的方式。數(shù)據(jù)結(jié)構(gòu)是指相互之間存在種或多種特定關(guān)系的數(shù)據(jù)元素的集合。通常情況下,精心選擇的
    發(fā)表于 11-17 14:45 ?1.6w次閱讀
    <b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>是什么_<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>有什么用

    什么是數(shù)據(jù)結(jié)構(gòu)?為什么要學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)?數(shù)據(jù)結(jié)構(gòu)的應(yīng)用實(shí)例分析

    本文檔的主要內(nèi)容詳細(xì)介紹的是什么是數(shù)據(jù)結(jié)構(gòu)?為什么要學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)?數(shù)據(jù)結(jié)構(gòu)的應(yīng)用實(shí)例分析包括了:數(shù)據(jù)結(jié)構(gòu)在串口通信當(dāng)中的應(yīng)用,數(shù)據(jù)結(jié)構(gòu)在按鍵
    發(fā)表于 09-26 15:45 ?14次下載
    什么是<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>?為什么要學(xué)習(xí)<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>?<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>的應(yīng)用實(shí)例分析

    如何解決數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)最大頻率棧問題?

    。 力扣第 895 題要求我們實(shí)現(xiàn)個(gè)特殊的數(shù)據(jù)結(jié)構(gòu)「最大頻率?!梗容^有意思,讓我們實(shí)現(xiàn)下面這兩個(gè)
    的頭像 發(fā)表于 03-02 11:02 ?1344次閱讀

    數(shù)據(jù)結(jié)構(gòu)解決滑動(dòng)窗口問題

    前文用 [單調(diào)棧解決三道算法問題]介紹了單調(diào)棧這種特殊數(shù)據(jù)結(jié)構(gòu),本文寫個(gè)類似的數(shù)據(jù)結(jié)構(gòu)「單調(diào)隊(duì)列」。 也許這種數(shù)據(jù)結(jié)構(gòu)的名字你沒聽過,其
    的頭像 發(fā)表于 04-19 10:50 ?564次閱讀
    <b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>解決滑動(dòng)窗口問題

    如何理解掌握J(rèn)ava數(shù)據(jù)結(jié)構(gòu)?

    Java 數(shù)據(jù)結(jié)構(gòu)是 Java 程序員必須掌握的重要知識(shí)之。
    的頭像 發(fā)表于 06-06 15:53 ?734次閱讀

    NetApp的數(shù)據(jù)結(jié)構(gòu)是如何演變的

    統(tǒng)一數(shù)據(jù)跨分布式資源進(jìn)行管理,以實(shí)現(xiàn)數(shù)據(jù)移動(dòng)的致性和控制,安全、可見性、保護(hù)和訪問。 本文定義了數(shù)據(jù)結(jié)構(gòu)及其體系
    發(fā)表于 08-25 17:15 ?0次下載
    NetApp的<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>是如何演變的

    epoll的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)

    先看下 eventpoll 這個(gè)數(shù)據(jù)結(jié)構(gòu),這個(gè)數(shù)據(jù)結(jié)構(gòu)是我們?cè)谡{(diào)用 epoll_create 之后內(nèi)核創(chuàng)建的個(gè)句柄,表示了
    的頭像 發(fā)表于 11-10 10:20 ?631次閱讀
    epoll的基礎(chǔ)<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>

    redis數(shù)據(jù)結(jié)構(gòu)的底層實(shí)現(xiàn)

    Redis是種內(nèi)存鍵值數(shù)據(jù)庫(kù),常用于緩存、消息隊(duì)列、實(shí)時(shí)數(shù)據(jù)分析等場(chǎng)景。它的高性能得益于其精心設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)和底層實(shí)現(xiàn)。本文將詳細(xì)介紹Re
    的頭像 發(fā)表于 12-05 10:14 ?521次閱讀