【Hot 100】 146. LRU 緩存

目錄

  • 引言
  • LRU 緩存
    • 官方解題
    • LRU實現
    • 📌 實現步驟分解
      • 步驟 1:定義雙向鏈表節點
      • 步驟 2:創建偽頭尾節點(關鍵設計)
      • 步驟 3:實現鏈表基礎操作
        • 操作 1:添加節點到頭部
        • 操作 2:移除任意節點
      • 步驟 4:實現關鍵組合操作
        • 操作 3:移動節點到頭部(訪問時調用)
        • 操作 4:移除尾部節點(淘汰時調用)
      • 步驟 5:初始化緩存結構
      • 步驟 6:實現 get 操作
      • 步驟 7:實現 put 操作
    • 🔑 關鍵設計驗證點
    • 🚀 完整實現代碼
    • 💡 實現要點總結

請添加圖片描述

  • 🙋?♂? 作者:海碼007
  • 📜 專欄:算法專欄
  • 💥 標題:【Hot 100】 146. LRU 緩存
  • ?? 寄語:書到用時方恨少,事非經過不知難!

引言

這題好像幾年前就是hard。后面變成medium了。感覺就是普通人只做1~2遍,都不能獨立記住整個實現過程。做到第3遍時大概能記得思路開始獨立寫代碼了,但是會遇到各種問題不能bug free的AC掉。需要練很多遍才能真的在面試中寫對的。這題應該就是靠代碼功底的,看能不能現場寫出bug free或者能debug出來。

上面的這個是別人寫的評論,看著確實是這么回事。今天能把這道題寫完就算ok了。這個相當于設計一個類了。

LRU 緩存

  • 🎈 題目鏈接:
  • 🎈 做題狀態:

官方解題

這道題涉及的知識面確實比較多,第一次做的話不容易ac。可以多寫幾次。

struct DLinkedNode {int key, value;DLinkedNode* 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 {
private:unordered_map<int, DLinkedNode*> cache;DLinkedNode* head;DLinkedNode* tail;int size;int capacity;public:LRUCache(int _capacity) : capacity(_capacity), size(0) {// 使用偽頭部和偽尾部節點head = new DLinkedNode();tail = new DLinkedNode();head->next = tail;tail->prev = head;}int get(int key) {// 判斷當前key是否存在if (!cache.count(key)){return -1;}// 通過哈希表快速的找到節點DLinkedNode* node = cache[key];moveToHead(node);return node->value;}void put(int key, int value) {// 判斷當前key是否存在if (!cache.count(key)){// 不存在,則創建一個新節點DLinkedNode* node = new DLinkedNode(key, value);// 將其添加到哈希表中cache[key] = node;// 將新的節點添加到雙向鏈表的頭部,因為是新使用的addToHead(node);++size;// 判斷當前鏈表大小是否超過容量,如果超出容量,則需要刪除尾部節點if (size > capacity){DLinkedNode* removed = removedTail();cache.erase(removed->key);delete removed;--size;}}else{// 如果當前key存在,先通過哈希表定位,再修改value,并移動到頭部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* removedTail(){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實現

我將把 LRU 緩存的實現拆解為 7 個清晰的步驟,并配以關鍵代碼片段。讓我們像搭積木一樣逐步構建這個數據結構:


📌 實現步驟分解

步驟 1:定義雙向鏈表節點

目的:創建存儲數據的基本單元

struct Node {int key;        // 用于刪除尾節點時反向查找哈希表int value;Node* prev;Node* next;Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
};

步驟 2:創建偽頭尾節點(關鍵設計)

目的:消除邊界條件判斷,簡化鏈表操作

class LRUCache {
private:Node* dummyHead; // 永遠指向虛擬頭部節點Node* dummyTail; // 永遠指向虛擬尾部節點// 初始化偽頭尾dummyHead = new Node(-1, -1);dummyTail = new Node(-1, -1);dummyHead->next = dummyTail;dummyTail->prev = dummyHead;

步驟 3:實現鏈表基礎操作

操作 1:添加節點到頭部
void addToHead(Node* node) {// 新節點的前后指針node->prev = dummyHead;node->next = dummyHead->next;// 調整原有鏈接dummyHead->next->prev = node;dummyHead->next = node;
}
操作 2:移除任意節點
void removeNode(Node* node) {node->prev->next = node->next;node->next->prev = node->prev;
}

步驟 4:實現關鍵組合操作

操作 3:移動節點到頭部(訪問時調用)
void moveToHead(Node* node) {removeNode(node);  // 從當前位置移除addToHead(node);   // 添加到頭部
}
操作 4:移除尾部節點(淘汰時調用)
Node* removeTail() {Node* node = dummyTail->prev; // 真實尾節點removeNode(node);return node;
}

步驟 5:初始化緩存結構

class LRUCache {
private:unordered_map<int, Node*> cache; // 哈希表int capacity;                    // 容量限制public:LRUCache(int cap) : capacity(cap) {// 初始化偽頭尾(見步驟2)}
};

步驟 6:實現 get 操作

int get(int key) {if (!cache.count(key)) return -1; // 未命中Node* node = cache[key];moveToHead(node);    // 更新為最近使用return node->value;
}

步驟 7:實現 put 操作

void put(int key, int value) {if (cache.count(key)) { // 已存在Node* node = cache[key];node->value = value;    // 更新值moveToHead(node);       // 設為最新} else {                   // 新增數據Node* node = new Node(key, value);cache[key] = node;      // 記錄到哈希表addToHead(node);        // 添加到鏈表if (cache.size() > capacity) {Node* removed = removeTail();  // 淘汰最舊cache.erase(removed->key);     // 從哈希表刪除delete removed;                // 釋放內存}}
}

🔑 關鍵設計驗證點

測試場景預期操作驗證方法
緩存未滿時插入新節點添加到頭部檢查鏈表頭部是否為最新節點
緩存已滿時插入淘汰尾節點后添加新節點到頭部檢查鏈表長度是否保持 capacity
訪問已有節點節點被移動到頭部執行get后檢查節點位置
更新已有節點的值值被更新且節點移到頭部檢查值變化和節點位置

🚀 完整實現代碼

#include <unordered_map>
using namespace std;struct Node {int key, value;Node *prev, *next;Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
};class LRUCache {
private:unordered_map<int, Node*> cache;Node *dummyHead, *dummyTail;int capacity;void addToHead(Node* node) {node->prev = dummyHead;node->next = dummyHead->next;dummyHead->next->prev = node;dummyHead->next = node;}void removeNode(Node* node) {node->prev->next = node->next;node->next->prev = node->prev;}void moveToHead(Node* node) {removeNode(node);addToHead(node);}Node* removeTail() {Node* node = dummyTail->prev;removeNode(node);return node;}public:LRUCache(int cap) : capacity(cap) {dummyHead = new Node(-1, -1);dummyTail = new Node(-1, -1);dummyHead->next = dummyTail;dummyTail->prev = dummyHead;}int get(int key) {if (!cache.count(key)) return -1;Node* node = cache[key];moveToHead(node);return node->value;}void put(int key, int value) {if (cache.count(key)) {Node* node = cache[key];node->value = value;moveToHead(node);} else {Node* node = new Node(key, value);cache[key] = node;addToHead(node);if (cache.size() > capacity) {Node* removed = removeTail();cache.erase(removed->key);delete removed;}}}
};

💡 實現要點總結

  1. 雙數據結構協作

    • 哈希表:O(1) 時間查找
    • 雙向鏈表:維護訪問順序
  2. 偽節點的妙用

    • 消除頭尾節點的特殊判斷
    • 統一所有節點的操作邏輯
  3. 操作原子化

    • 將鏈表操作分解為獨立方法
    • 提高代碼可讀性和可維護性
  4. 內存管理

    • 淘汰節點時需手動釋放內存
    • 插入新節點時動態分配內存

通過這種分步實現方式,可以更清晰地理解每個組件的作用,也便于在開發過程中逐步測試驗證每個功能的正確性。

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

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

相關文章

【Linux】swap交換分區管理

目錄 一、Swap 交換分區的功能 二、swap 交換分區的典型大小的設置 2.1 查看交換分區的大小 2.1.1 free 2.1.2 cat /proc/swaps 或 swapon -s 2.1.3 top 三、使用交換分區的整體流程 3.1 案例一 3.2 案例二 一、Swap 交換分區的功能 計算機運行一個程序首先會將外存&am…

【計算機網絡】用戶從輸入網址到網頁顯示,期間發生了什么?

1.URL解析 瀏覽器分解URL&#xff1a;https://www.example.com/page 協議&#xff1a;https域名&#xff1a;www.example.com路徑&#xff1a;/page 2.DNS查詢&#xff1a; 瀏覽器向DNS服務器發送查詢請求&#xff0c;將域名解析為對應的IP地址。 3.CDN檢查(如果有)&#…

架空輸電線巡檢機器人軌跡優化設計

架空輸電線巡檢機器人軌跡優化 摘要 本論文針對架空輸電線巡檢機器人的軌跡優化問題展開研究,綜合考慮輸電線復雜環境、機器人運動特性及巡檢任務需求,結合路徑規劃算法、智能優化算法與機器人動力學約束,構建了多目標軌跡優化模型。通過改進遺傳算法與模擬退火算法,有效…

根據窗口大小自動調整頁面縮放比例,并保持居中顯示

vue 項目 直接上代碼 圖片u1.png 是個背景圖片 圖片u2.png 是個遮罩 <template><div id"app"><div class"viewBox"><divclass"screen":style"{ transform: translate(-50%,-50%…

初學Python爬蟲

文章目錄 前言一、 爬蟲的初識1.1 什么是爬蟲1.2 爬蟲的核心1.3 爬蟲的用途1.4 爬蟲分類1.5 爬蟲帶來的風險1.6. 反爬手段1.7 爬蟲網絡請求1.8 爬蟲基本流程 二、urllib庫初識2.1 http和https協議2.2 編碼解碼的使用2.3 urllib的基本使用2.4 一個類型六個方法2.5 下載網頁數據2…

oracle 數據庫sql 語句處理過程

14.1SQL語句處理過程 在進行SQL語句處理優化前&#xff0c;需要先熟悉和了解SQL語句的處理過程。 每種類型的語句在執行時都需要如下階段&#xff1a; 第1步: 創建游標。 第2步: 分析語句。 第5步: 綁定變量。 第7步: t運行語句。 第9步: 關閉游標。 如果使用了并行功能&#x…

pm2 list查詢服務時如何通過name或者namespace進行區分

在 PM2 中&#xff0c;如果 pm2 list 顯示的所有服務名稱&#xff08;name&#xff09;相同&#xff0c;就無法直觀地區分不同的進程。這時可以通過 --namespace&#xff08;命名空間&#xff09; 或 自定義 name 來區分服務。以下是解決方案&#xff1a; 方法 1&#xff1a;啟…

[python] 函數基礎

二 函數參數 2.1 必備參數(位置參數) 含義: 傳遞和定義參數的順序及個數必須一致 格式: def func(a,b) def func_1(id,passwd):print("id ",id)print("passwd ",passwd) func_1(10086,123456) 2.2 默認參數 函數: 為函數的參數提供一個默認值,如果調…

超大規模SoC后仿真流程與優化

在超大規模SoC設計中,是否需要進行全芯片后仿真(Full-Chip Post-layout Simulation)取決于多個因素,包括設計復雜度、項目風險、資源限制以及驗證目標。以下是針對這一問題的系統性分析: 1. 全芯片后仿真的必要性 需要全芯片后仿真的場景 系統級交互驗證: 跨模塊信號交互…

深入理解 Docker 網絡原理:構建高效、靈活的容器網絡

在現代軟件開發中&#xff0c;Docker 已經成為了容器化技術的代名詞&#xff0c;廣泛應用于開發、測試和生產環境。Docker 使得開發者能夠將應用及其依賴打包成一個輕量級的容器&#xff0c;并通過 Docker 容器化技術來實現高效的部署與管理。 然而&#xff0c;在日常使用 Dock…

leetcode 242. Valid Anagram

題目描述 因為s和t僅僅包含小寫字母&#xff0c;所以可以開一個26個元素的數組用來做哈希表。不過如果是unicode字符&#xff0c;那就用編程語言自帶的哈希表。 class Solution { public:bool isAnagram(string s, string t) {int n s.size();if(s.size() ! t.size())return …

4、反應釜壓力監控系統 - /自動化與控制組件/reaction-vessel-monitor

76個工業組件庫示例匯總 反應釜壓力監控組件 這是一個用于反應釜壓力監控的自定義組件&#xff0c;專為化工廠反應釜壓力監控設計。采用蘋果工業風格界面&#xff0c;簡潔優雅&#xff0c;功能實用&#xff0c;易于使用。 功能特點 實時壓力可視化&#xff1a;直觀展示反應…

系統思考助力富維東陽

剛剛完成了長春一家汽車零配件公司關于系統思考的項目&#xff01; 在開班儀式上&#xff0c;公司總經理深刻闡述了項目的背后意義&#xff0c;強調了系統思考與公司戰略的緊密聯系。這不僅是一次培訓&#xff0c;更是一次關于“如何全方位看待問題”的深度對話。 在這個過程中…

Linux下的c/c++開發之操作Sqlite3數據庫

libsqlite3-dev 介紹&#xff08;Linux 下的 SQLite3 C/C 開發包&#xff09; libsqlite3-dev 是一個開發包&#xff0c;在 Linux 環境下為使用 SQLite3 C API 進行開發的 C/C 程序員提供頭文件&#xff08;如 sqlite3.h&#xff09;和靜態庫/動態庫的鏈接信息&#xff08;如 …

【Prompt工程—文生圖】案例大全

目錄 一、人物繪圖 二、卡通頭像 三、風景圖 四、logo設計圖 五、動物形象圖 六、室內設計圖 七、動漫風格 八、二次元圖 九、日常場景圖 十、古風神化圖 十一、游戲場景圖 十二、電影大片質感 本文主要介紹了12種不同類型的文生圖技巧&#xff0c;通過加入不同的圖像…

GMRES算法處理多個右端項的Block與PseudoBlock變體

GMRES算法處理多個右端項的Block與PseudoBlock變體 Block與PseudoBlock GMRES簡介 在處理多個右端項的線性方程組時&#xff0c;Block GMRES和PseudoBlock GMRES是兩種常用的變體算法&#xff1a; Block GMRES&#xff1a;同時處理所有右端項&#xff0c;構建一個大的Krylov…

Ubuntu環境下如何管理系統中的用戶:創建用戶、刪除用戶、修改密碼、切換用戶、用戶組管理

管理用戶的操作需要root權限&#xff0c;在執行命令時需要加sudo&#xff0c;關于sudo命令可以看這篇&#xff1a;Linux_sudo命令的使用與機制 1、添加用戶 使用命令&#xff1a; adduser 用戶名&#xff0c;主要是按提示輸入密碼和用戶信息&#xff08;可直接回車使用默認配置…

開源BI選型及DataEase搭建

工具名稱 國家/社區技術棧核心功能國內適用性國外適用性推薦場景Apache Superset美國&#xff08;Apache&#xff09;Python/React可視化、SQL Lab、多數據源、插件擴展需自行漢化&#xff0c;社區支持較少生態完善&#xff0c;云原生支持好&#xff08;AWS/GCP&#xff09;中大…

云計算-容器云-部署jumpserver 版本1

部署jumpserver [root@jumpserver ~]# tar -zxvf jumpserver.tar.gz -C /opt/ [root@jumpserver ~]# ls /opt/ compose config docker docker.service images jumpserver-repo static.env將默認Yum源移至其他目錄,創建本地Yum源文件,命令及文件內容如下: [root@jumpserver…

利用Elixir中的原子特性 + 錯誤消息泄露 -- Atom Bomb

題目信息: This new atom bomb early warning system is quite strange… 題目使用 elixir 語言 一開始,我們會訪問 /page.html <!DOCTYPE html> <!-- 設定文檔語言為英語 --> <html lang"en"> <head><!-- 設定字符編碼為UTF-8 --><…