【LeetCode刷題】146. LRU 緩存

請你設計并實現一個滿足??LRU (最近最少使用) 緩存?約束的數據結構。

實現?LRUCache?類:

  • LRUCache(int capacity)?以?正整數?作為容量?capacity?初始化 LRU 緩存
  • int get(int key)?如果關鍵字?key?存在于緩存中,則返回關鍵字的值,否則返回?-1?。
  • void put(int key, int value)?如果關鍵字?key?已經存在,則變更其數據值?value?;如果不存在,則向緩存中插入該組?key-value?。如果插入操作導致關鍵字數量超過?capacity?,則應該?逐出?最久未使用的關鍵字。

函數?get?和?put?必須以?O(1)?的平均時間復雜度運行。

示例:

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

思路:這道題的難點在于記錄最近最少使用,使用map可以滿足get的O(1),但是無法記錄最近最少使用的數據;如果使用數組,刪除/增加的時間復雜度則是O(n),也不滿足。

使用哈希表 + 雙向鏈表可以滿足刪除/增加的時間復雜度為O(1)。

這個圖太形象了。

(1)雙向鏈表按照被使用的順序存儲了這些鍵值對,靠近頭部的鍵值對是最近使用的,而靠近尾部的鍵值對是最久未使用的

(2)哈希表即為普通的哈希映射(HashMap),通過緩存數據的鍵映射到其在雙向鏈表中的位置。

(3)對于 get 操作,首先判斷 key 是否存在:

????????(a)如果 key 不存在,則返回 ?1;

????????(b)如果 key 存在,則 key 對應的節點是最近被使用的節點。通過哈希表定位到該節點在雙向鏈表中的位置,并將其移動到雙向鏈表的頭部,最后返回該節點的值。

(3)對于 put 操作,首先判斷 key 是否存在:

????????(a)如果 key 不存在,使用 key 和 value 創建一個新的節點,在雙向鏈表的頭部添加該節點,并將 key 和該節點添加進哈希表中。然后判斷雙向鏈表的節點數是否超出容量,如果超出容量,則刪除雙向鏈表的尾部節點,并刪除哈希表中對應的項;

????????(b)如果 key 存在,則與 get 操作類似,先通過哈希表定位,再將對應的節點的值更新為 value,并將該節點移到雙向鏈表的頭部。

思路很清晰

class LRUCache {
public:LRUCache(int capacity) {}int get(int key) {}void put(int key, int value) {}
};/*** Your LRUCache object will be instantiated and called as such:* LRUCache* obj = new LRUCache(capacity);* int param_1 = obj->get(key);* obj->put(key,value);*/

一步步實現:

(1)定義雙鏈表

struct DLinkedNode {int key, value;             // k-vDLinkedNode* prev;          // 前向指針DLinkedNode* next;          // 后向指針// 兩個構造函數DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

(2)在LRUCache類中添加成員屬性:哈希表+雙向鏈表

class LRUCache {
public:// 新加的unordered_map<int, DLinkedNode*> cache;DLinkedNode* head;            // 偽頭節點,不存數據DLinkedNode* tail;            // 偽尾節點,不存數據int size;                     // 當前存儲的數量,當size==capacity時,要移出數據了int capacity;                 // 容量// 實現構造函數LRUCache(int _capacity): capacity(_capacity), size(0) {// 使用偽頭節點和偽尾節點,不存數據head = new DLinkedNode();tail = new DLinkedNode();// 開始時一個數據都沒有head->next = tail;tail->prev = head;}int get(int key) {}void put(int key, int value) {}
};

(3)實現雙向鏈表中的【在頭部添加數據】、【任意位置刪除數據】、【數據移動到頭部】、【從尾部刪除數據】

在頭部添加數據

    // 在頭部添加數據void addToHead(DLinkedNode* node) {node->prev = head;node->next = head->next;head->next->prev = node;head->next = node;}

任意位置刪除數據

    // 任意位置刪除數據void removeNode(DLinkedNode* node) {node->prev->next = node->next;node->next->prev = node->prev;}

數據移動到頭部

    // 移動數據到頭部void moveToHead(DLinkedNode* node) {removeNode(node);addToHead(node);}

從尾部刪除數據

    // 從尾部刪除數據DLinkedNode* reoveTail() {DLinkedNode* node = tail->prev;removeNode(node);return node;}

(4)實現get函數

如果不存在直接返回-1,存在的話,先通過哈希表定位,再移動到頭部

    int get(int key) {// 不存在if (cache.count(key) == 0) {return -1;}// 通過哈希找到,移動到頭部DLinkedNode* node = cache[key];moveToHead(node);return node->value;}

(5)實現put函數

如果key不存在,則創建一個節點,注意size==capacity的情況,此時刪除隊尾數據

靠近頭部的鍵值對是最近使用的,而靠近尾部的鍵值對是最久未使用的

如果存在,修改value,再將該節點移動到隊頭

 void put(int key, int value) {// 不存在if (cache.count(key) == 0) {DLinkedNode* node = new DLinkedNode(key, value);cache[key] = node;          // 添加到哈希表中addToHead(node);            // 移動到隊頭size++;if (size > capacity) {DLinkedNode* removeNode = reoveTail();  // 刪除尾部數據cache.erase(removeNode->key);           // 刪除哈希中的數據delete removeNode;size--; }} else {DLinkedNode* node = cache[key];node->value = value;moveToHead(node);               // 移到隊頭}}

全部代碼實現

struct DLinkedNode {int key, value;             // k-vDLinkedNode* prev;          // 前向指針DLinkedNode* next;          // 后向指針// 兩個構造函數DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};class LRUCache {
public:unordered_map<int, DLinkedNode*> cache;DLinkedNode* head;DLinkedNode* tail;int size;int capacity;LRUCache(int _capacity): capacity(_capacity), size(0) {// 使用偽頭節點和偽偽節點,不存數據head = new DLinkedNode();tail = new DLinkedNode();// 開始時一個數據都沒有head->next = tail;tail->prev = head;}int get(int key) {// 不存在if (cache.count(key) == 0) {return -1;}// 通過哈希找到,移動到頭部DLinkedNode* node = cache[key];moveToHead(node);return node->value;}void put(int key, int value) {// 不存在if (cache.count(key) == 0) {DLinkedNode* node = new DLinkedNode(key, value);cache[key] = node;          // 添加到哈希表中addToHead(node);            // 移動到隊頭size++;if (size > capacity) {DLinkedNode* removeNode = reoveTail();  // 刪除尾部數據cache.erase(removeNode->key);           // 刪除哈希中的數據delete removeNode;size--; }} else {DLinkedNode* node = cache[key];node->value = value;moveToHead(node);               // 移到隊頭}}// 在頭部添加數據void addToHead(DLinkedNode* node) {node->prev = head;node->next = head->next;head->next->prev = node;head->next = node;}// 任意位置刪除數據void removeNode(DLinkedNode* node) {node->prev->next = node->next;node->next->prev = node->prev;}// 移動數據到頭部void moveToHead(DLinkedNode* node) {removeNode(node);addToHead(node);}// 從尾部刪除數據DLinkedNode* reoveTail() {DLinkedNode* node = tail->prev;removeNode(node);return node;}};/*** Your LRUCache object will be instantiated and called as such:* LRUCache* obj = new LRUCache(capacity);* int param_1 = obj->get(key);* obj->put(key,value);*/

參考:【字節一面】 LRU Cache 實現剖析_嗶哩嗶哩_bilibili

鏈接:. - 力扣(LeetCode)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/714552.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/714552.shtml
英文地址,請注明出處:http://en.pswp.cn/news/714552.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

全量知識系統問題及SmartChat給出的答復 之9 三套工具之4語法解析器 之2

Q23. 一個語言的語法簡約規則 這些規則顯示show 在一個給定單詞&#xff08;a given word&#xff09;的右邊或左邊可能出現的單詞的類別。句型的多樣性variety不是復雜文法&#xff08;a complex grammar&#xff09;的結果&#xff0c;而是簡單語法&#xff08;a simple gra…

【InternLM 實戰營筆記】浦語·靈筆的圖文理解及創作部署、 Lagent 工具調用 Demo

浦語靈筆的圖文理解及創作部署 浦語靈筆是基于書生浦語大語言模型研發的視覺-語言大模型&#xff0c;提供出色的圖文理解和創作能力&#xff0c;結合了視覺和語言的先進技術&#xff0c;能夠實現圖像到文本、文本到圖像的雙向轉換。使用浦語靈筆大模型可以輕松的創作一篇圖文推…

進程間的通信 -- 共享內存

一 共享內存的概念 1. 1 共享內存的原理 之前我們學過管道通信&#xff0c;分為匿名管道和命名管道&#xff0c;匿名管道通過父子進程的屬性繼承原理來完成父子進程看到同一份資源的目的&#xff0c;而命名管道則是通過路徑與文件名來唯一標識管道文件&#xff0c;來讓不同的進…

學習Android的第二十一天

目錄 Android ProgressDialog (進度條對話框) 例子 Android DatePickerDialog 日期選擇對話框 例子 Android TimePickerDialog 時間選擇對話框 Android PopupWindow 懸浮框 構造函數 方法 例子 官方文檔 Android OptionMenu 選項菜單 例子 官方文檔 Android Progr…

Java實戰:Spring Boot中各類參數校驗機制

引言 在開發Web應用程序時&#xff0c;對客戶端傳入的參數進行有效校驗是保證系統安全性和穩定性的重要環節。Spring Boot作為一個現代化的Java開發框架&#xff0c;提供了多種參數校驗的方法和工具&#xff0c;以滿足不同場景下的需求。本文將深入探討Spring Boot中實現各種參…

typescript 的常用方式

文章目錄 前言一、綁定props 默認值的方式&#xff1a;withDefaults1.vue2 的props設置默認值2.vue3 的props設置默認值(1) 不設置默認值的寫法(2) 設置默認值的寫法&#xff08;分離模式&#xff09;(3) 設置默認值的寫法&#xff08;組合模式&#xff09; 二、定義一個二維數…

Matlab在同一張圖中如何加入多個圖例

根據代碼最終畫出的圖片如下&#xff1a; 其實原理很簡單&#xff0c;就是在一張figure中畫多個坐標軸&#xff0c;每個坐標軸都有對應的圖例&#xff0c;之后再將多余坐標軸隱藏&#xff0c;只保留一個即可。 代碼如下&#xff1a; clear all; close all;dd_linewidth 1;a …

maven archetype 項目原型

拓展閱讀 maven 包管理平臺-01-maven 入門介紹 Maven、Gradle、Ant、Ivy、Bazel 和 SBT 的詳細對比表格 maven 包管理平臺-02-windows 安裝配置 mac 安裝配置 maven 包管理平臺-03-maven project maven 項目的創建入門 maven 包管理平臺-04-maven archetype 項目原型 ma…

Spring學習筆記(六)利用Spring的jdbc實現學生管理系統的用戶登錄功能

一、案例分析 本案例要求學生在控制臺輸入用戶名密碼&#xff0c;如果用戶賬號密碼正確則顯示用戶所屬班級&#xff0c;如果登錄失敗則顯示登錄失敗。 &#xff08;1&#xff09;為了存儲學生信息&#xff0c;需要創建一個數據庫。 &#xff08;2&#xff09;為了程序連接數…

洛谷P1927防護傘

題目描述 據說 20122012 的災難和太陽黑子的爆發有關。于是地球防衛小隊決定制造一個特殊防護傘&#xff0c;擋住太陽黑子爆發的區域&#xff0c;減少其對地球的影響。由于太陽相對于地球來說實在是太大了&#xff0c;我們可以把太陽表面看作一個平面&#xff0c;中心定為(0,0…

C 基本語法

我們已經看過 C 程序的基本結構&#xff0c;這將有助于我們理解 C 語言的其他基本的構建塊。 C 的令牌&#xff08;Token&#xff09; C 程序由各種令牌組成&#xff0c;令牌可以是關鍵字、標識符、常量、字符串值&#xff0c;或者是一個符號。例如&#xff0c;下面的 C 語句…

30天自制操作系統(第23天)

23.1 編寫malloc 參考第22天的內容&#xff0c;在繪制窗口前先分配了150*50個字節大小的內存&#xff0c;所以導致該文件經編譯后有7.6k左右&#xff0c;能否在其中使用指針呢&#xff1f;當需要開辟空間時&#xff0c;移動指針即可。在之前的章節中也有函數memman_alloc函數可…

php源碼 單色bmp圖片取模工具 按任意方式取模 生成字節數組 自由編輯點陣

http://2.wjsou.com/BMP/index.html 想試試chatGPT4生成&#xff0c;還是要手工改 php 寫一個網頁界面上可以選擇一張bmp圖片&#xff0c;界面上就顯示這張bmp圖片&#xff0c; 點生成取模按鈕&#xff0c;在圖片下方會顯示這張bmp圖片的取模數據。 取模規則是按界面設置的&a…

Linux 的交換空間(swap)是什么?有什么用?

目錄 swap是什么&#xff1f;swap有什么用&#xff1f;swap使用典型場景如何查看你的系統是否用到交換空間呢&#xff1f;查看系統中swap in/out的情況 swap是什么&#xff1f; swap就是磁盤上的一塊區域。它和Windows系統中的交換文件作用類似&#xff0c;但是它是一段連續的…

03、MongoDB -- MongoDB 權限的設計

目錄 MongoDB 權限的設計演示前準備&#xff1a;啟動 mongodb 服務器 和 客戶端 &#xff1a;1、啟動單機模式的 mongodb 服務器2、啟動 mongodb 的客戶端 MongoDB 權限的設計1、MongoDB 的每個數據庫都可以保存用戶&#xff0c;不止admin數據庫可以保存用戶。2、保存用戶的數據…

Linux 學習筆記(8)

八、 啟動引導 1 、 Linux 的啟動流程 1) BIOS 自檢 2) 啟動 GRUB/LILO 3) 運行 Linux kernel 并檢測硬件 4) 掛載根文件系統 5) 運行 Linux 系統的第一個進程 init( 其 PID 永遠為 1 &#xff0c;是所有其它進程的父進程 ) 6) init 讀取系統引導配置文件…

GD25Q32驅動

GD25Q32是一款基于SPI的Flash芯片&#xff0c;容量為32/84M bytes。它的引腳如下&#xff1a; 該芯片支持多種SPI操作方式&#xff0c;包括&#xff1a;Standard SPI(標準SPI)、Dual SPI(雙線 SPI)和Quad SPI(四線 SPI) 。有關SPI的介紹可以參考&#xff1a; SPI通信原理-CSDN…

flutter 文字一行顯示,超出換行

因為app有多語言&#xff0c;中文和其他語言長度不一致&#xff0c;可能導致英文會很長。 中文樣式 英文樣式 代碼 Row(mainAxisAlignment: MainAxisAlignment.end,crossAxisAlignment: CrossAxisAlignment.end,children: [Visibility(visible: controller.info.fee ! null,ch…

探尋2024年國內熱門低代碼平臺排行!| 功能特點一覽

低代碼開發是一項革命性的技術&#xff0c;主要目的是盡量避免程序研發的復雜性&#xff0c;讓外行開發者也能加入到應用程序的搭建中。低代碼平臺的核心概念和構成部分通常包括用戶界面和拖拽設計、預構件和模塊、自動化工作內容與數據庫集成和擴展應用&#xff0c;應用低代碼…

web前端css基本內容

web前端css 當我們用html的語法給內容規劃布局樣式時&#xff0c;可能會出現許多個相似的內容需要運用同一種樣式&#xff0c;復制粘貼太麻煩而且如果后期要改動的話比如把許多個地方從紅色改成藍色&#xff0c;就需要一個一個改了&#xff0c;這時候就需要引入css來操作了 把…