手撕LFU

博主介紹:程序喵大人

  • 35- 資深C/C++/Rust/Android/iOS客戶端開發
  • 10年大廠工作經驗
  • 嵌入式/人工智能/自動駕駛/音視頻/游戲開發入門級選手
  • 《C++20高級編程》《C++23高級編程》等多本書籍著譯者
  • 更多原創精品文章,首發gzh,見文末
  • 👇👇記得訂閱專欄,以防走丟👇👇
    😉C++基礎系列專欄
    😃C語言基礎系列專欄
    🤣C++大佬養成攻略專欄
    🤓C++訓練營
    👉🏻個人網站

最近是校招實習面試高峰期,訓練營中很多同學都在面試中,有些同學甚至被大廠要求手撕LFU,覺得很離譜。

但其實手撕LFU在面試中已經不少見了,手撕LFU、手撕LRU,在現在的面試中都很常見,大家一定要掌握,平時可以多練幾遍。

對應的力扣鏈接如下:

  • https://leetcode.cn/problems/lfu-cache/
  • https://leetcode.cn/problems/lru-cache/

相關LRU題解如下:

下面通過一個例子,來給大家說一下 LRU 的概念。

假設你是一位圖書管理員,你需要管理一個熱門書籍借閱專區,空間有限,只能存放一定數量的書籍。讀者借閱書籍后需歸還,當專區滿了,又有新的熱門書籍要上架時,你會怎么做呢?

你大概會去查看借閱記錄,看哪些書籍被頻繁借閱,頻繁被借閱(相當于被訪問)的書籍會繼續留在專區,而那些很久都沒有讀者借閱(長時間未被訪問 )的書籍,會將其從專區移除,放到普通書架,把空間讓給新的熱門書籍。

上面這個例子,就是我們常說的 LRU 緩存淘汰算法。

既然知道了 LRU 原理,下面我們來看一下題目要求

那我們來拆解一下,需要做哪些工作

  1. 設計一個數據結構,用來存儲元素
  2. 維護數據結構里面的元素序列,能夠做到需要騰出空間時,可以快速逐出最久未使用的關鍵字
  3. 能夠快速的通過 key 獲取 value,也就是做到隨機訪問

需求已經很明確了,那我們此時應該選擇什么數據結構呢?因為需要快速獲取 value,并且要 put key 和 value,那么數據結構肯定要有 HashMap。

在 LRU 算法里,我們要維護元素的訪問順序,每次訪問一個節點,無論是新節點還是已有節點,都要把它移到有序序列的頭部,以此表示它是最新被訪問的。

這個有序序列會始終保持從頭部到尾部,節點未被訪問的時間依次遞增。也就是說,序列頭部的節點是剛剛被訪問過的,而尾部的節點則是最久未被訪問的,在需要淘汰元素時,就優先淘汰尾部節點。

以上所述,我們可以使用 **哈希表+鏈表 **來完成我們的需求。

那應該使用單向鏈表,還是雙向鏈表呢?

移動節點到鏈表頭部或刪除鏈表尾部節點,都需先刪除目標節點。

尋找后繼節點時,單雙向鏈表均可通過next指針在 O (1) 時間完成;但尋找前驅節點,單向鏈表需從頭遍歷,耗時 O (n),雙向鏈表則能借前向指針在 O (1) 時間找到。因此,為保證操作均在 O (1) 時間內完成,故應選擇雙向鏈表,本質是以空間換時間。

好了,我們來看一下,具體是如何存儲元素的呢?

從上圖可知,我們的 key 為 int,value 為雙向鏈表的節點

好啦,現在我們已經清晰知道了,應該如何設計數據結構,我們進一步根據題目,來了解需求

  1. LRUCache(int capacity)正整數 作為容量 capacity 初始化 LRU 緩存

解析: HashMap 需要有 size,并初始化 map 的 key 為 int,value 為雙向鏈表的節點

  1. int get(int key) 如果關鍵字 key 存在于緩存中,則返回關鍵字的值,否則返回 -1

解析: 如果不存在返回 -1,如果存在,則將該 value 返回,并且將該節點,移到雙向鏈表的頭部,移到鏈表頭部的操作我們可以分兩步進行

第一步:將節點從當前位置刪除

第二步:在鏈表頭部 add 該節點

具體操作如下

這樣就實現了,將某節點,移動到頭部的操作

  1. void put(int key, int value) 如果關鍵字 key 已經存在,則變更其數據值 value 并將其移動到鏈表頭部;如果不存在,則向緩存中插入該組 key-value 同時在鏈表頭部插入該節點。如果插入操作導致關鍵字數量會超過 capacity ,則應該 逐出 最久未使用的關鍵字后再插入

解析:這里有兩點需要注意,第一點,put 時,假設該節點存在,則需要將其放到頭節點。第二點如果超出緩存容量,則需要先刪除節點,再在頭部插入新節點。

整體思路已經了解,我們來看代碼吧

注:代碼中也有詳細的解析,請認真閱讀代碼

class LRUCache {
private:
// 鏈表的節點結構體,因為是雙向鏈表,需要有 perv,next,value,key,
// 這里有人問了,為什么還需要添加 key 呢?
// 因為刪去最近最少使用的鍵值對時,要刪除鏈表的尾節點
// 如果節點中沒有存儲 key,那么就無法知道,被刪除的是那個節點,也無法刪除 map 中對應的 key-value
// 此時,我們是先確定要刪除的鏈表節點,再去 map 中刪除對應的 key-value
struct DouLink {
int key;
int value;
DouLink* prev;
DouLink* next;
DouLink() : key(0), value(0), prev(nullptr), next(nullptr) {}
DouLink(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
};DouLink* head; // 虛擬頭節點
DouLink* tail; // 虛擬尾節點,幫助我們來完成頭插法和尾部刪除節點
int capacity; // map 的容量
int size; // 當前節點數目
// 我們不要求有序,所以使用 unordered_map 即可,提升性能
std::unordered_map<int, DouLink*> map; 
public:
// 構造函數初始化,只有虛擬頭尾節點的雙向鏈表,并配置,緩存容量
LRUCache(int capacity) : capacity(capacity), size(0) {head = new DouLink();tail = new DouLink();head->next = tail;tail->prev = head;
}// 釋放
~LRUCache() {DouLink* current = head;while (current != nullptr) {DouLink* nextNode = current->next;delete current;current = nextNode;}
}// 獲取節點內容
int get(int key) {auto it = map.find(key);// 未發現返回 -1,符合題目要求if (it == map.end()) {return -1;}// 訪問節點DouLink* temp = it->second;// 將最新被訪問的節點,移到鏈表頭部moveHead(temp);// 返回值return temp->value;
}
// put 有兩種情況,原先是否存該值
void put(int key, int value) {auto it = map.find(key);// 不存在if (it == map.end()) {// 增加新元素前,判斷是否需要清理空間(鏈表尾部節點,長時間未被訪問節點)if (size == capacity) {DouLink* newNode = removeTail();map.erase(newNode->key);delete newNode;--size;}// 初始化節點,并執行插入到鏈表頭部DouLink* test = new DouLink(key, value); addHead(test);// map 也執行對應的插入 key-valuemap[key] = test;++size; // 記錄當前存儲元素數目} else {// 存在該節點,修改節點內容DouLink* temp = it->second;temp->value = value;moveHead(temp);}
}// 封裝的操作鏈表函數,添加到鏈表頭部
void addHead(DouLink* node) {node->next = head->next;head->next->prev = node;head->next = node;node->prev = head;
}
// 封裝的操作鏈表函數,移動到鏈表頭部
void moveHead(DouLink* node) {remove(node); // 刪除節點addHead(node); // 將節點添加到頭部
}// 刪除某節點
void remove(DouLink* node) {node->prev->next = node->next;node->next->prev = node->prev;
}// 刪除鏈表尾部節點,長時間未被訪問節點
DouLink* removeTail() {DouLink* temp = tail->prev;remove(temp);return temp;
}
};

碼字不易,歡迎大家點贊關注評論,謝謝!


C++訓練營

專為校招、社招3年工作經驗的同學打造的1V1 C++訓練營,量身定制學習計劃、每日代碼review,簡歷優化,面試輔導,已幫助多名學員獲得大廠offer!

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

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

相關文章

火影bug,未保證短時間數據一致性,拿這個例子講一下Redis

本文只拿這個游戲的bug來舉例Redis&#xff0c;如果有不妥的地方&#xff0c;聯系我進行刪除 描述&#xff1a;今天在高速上打火影&#xff08;有隧道&#xff0c;有時候會卡&#xff09;&#xff0c;發現了個bug&#xff0c;我點了兩次-1000的忍玉&#xff08;大概用了1千七百…

KRaft (Kafka 4.0) 集群配置指南(超簡單,脫離 ZooKeeper 集群)還包含了簡化測試指令的腳本!!!

docker-compose方式部署kafka集群 Kafka 4.0 引入了 KRaft 模式&#xff08;Kafka Raft Metadata Mode&#xff09;&#xff0c;它使 Kafka 集群不再依賴 ZooKeeper 進行元數據管理。KRaft 模式簡化了 Kafka 部署和管理&#xff0c;不需要額外配置 ZooKeeper 服務&#xff0c;…

Admyral - 可擴展的GRC工程自動化平臺

文章目錄 一、關于 Admyral相關鏈接資源關鍵特性 二、安裝系統要求 三、快速開始1、啟動服務 四、核心功能1、自動化即代碼2、AI增強工作流3、雙向同步編輯器4、工作流監控5、企業級基礎設施 五、示例應用六、其他信息許可證遙測說明 一、關于 Admyral Admyral 是一個基于 Pyt…

DDR在PCB布局布線時的注意事項及設計要點

一、布局注意事項 控制器與DDR顆粒的布局 靠近原則&#xff1a;控制器與DDR顆粒應盡量靠近&#xff0c;縮短時鐘&#xff08;CLK&#xff09;、地址/控制線&#xff08;CA&#xff09;、數據線&#xff08;DQ/DQS&#xff09;的走線長度&#xff0c;減少信號延遲差異。 分組隔…

計算機網絡-LDP工作過程詳解

前面我們已經學習了LDP的基礎概念&#xff0c;了解了LDP會話的建立、LDP的標簽控制等知識&#xff0c;今天來整體過一遍LDP的一個工作過程&#xff0c;后面我們再通過實驗深入學習。 一、LDP標簽分發 標簽分發需要基于基礎的路由協議建立LDP會話&#xff0c;激活MPLS和LDP。以…

解構與重構:自動化測試框架的進階認知之旅

目錄 一、自動化測試的介紹 &#xff08;一&#xff09;自動化測試的起源與發展 &#xff08;二&#xff09;自動化測試的定義與目標 &#xff08;三&#xff09;自動化測試的適用場景 二、什么是自動化測試框架 &#xff08;一&#xff09;自動化測試框架的定義 &#x…

跑不出的循環 | LoveySelf 系列定位

最近開始陷入一輪一輪的循環狀態&#xff0c;無奈&#xff0c;只能自我整理一下。23年暑假&#xff0c;在計算機系折騰了一年后&#xff0c;重新打開博客&#xff0c;回想在數學系摸索博客寫作的日子&#xff0c;思緒涌上心頭&#xff0c;我們決定拾起這份力量。當時覺得 hexo …

Redis最新入門教程

文章目錄 Redis最新入門教程1.安裝Redis2.連接Redis3.Redis環境變量配置4.入門Redis4.1 Redis的數據結構4.2 Redis的Key4.3 Redis-String4.4 Redis-Hash4.5 Redis-List4.6 Redis-Set4.7 Redis-Zset 5.在Java中使用Redis6.緩存雪崩、擊穿、穿透6.1 緩存雪崩6.2 緩沖擊穿6.3 緩沖…

一文讀懂Python之requests模塊(36)

一、requests模塊簡介 requests模塊是python中原生的一款基于網絡請求的模塊&#xff0c;功能強大&#xff0c;簡單便捷且高效 &#xff0c;該模塊可以模擬瀏覽器發送請求&#xff0c;主要包括指定url、發起請求、獲取響應數據和持久化存儲&#xff0c;包括 GET、POST、PUT、…

WPF之布局流程

文章目錄 1. 概述2. 布局元素的邊界框3. 布局系統原理3.1 布局流程時序圖 4. 測量階段(Measure Phase)4.1 測量過程4.2 MeasureOverride方法 5. 排列階段(Arrange Phase)5.1 排列過程5.2 ArrangeOverride方法 6. 渲染階段(Render Phase)7. 布局事件7.1 主要布局事件7.2 布局事件…

uniapp|獲取當前用戶定位、與系統設定位置計算相隔米數、實現打卡簽到(可自定義設定位置、位置有效范圍米數)

基于UniApp闡述移動應用開發中定位功能的實現全流程,涵蓋實時定位獲取、動態距離計算與自定義位置、有效范圍設定等功能。文章提供完整的代碼示例與適配方案,適用于社交簽到、課堂教室打卡等場景。 目錄 引言定位功能在移動應用中的價值(社交、導航、O2O等場景)UniApp跨平臺…

Yii2.0 模型規則(rules)詳解

一、基本語法結構 public function rules() {return [// 規則1[[attribute1, attribute2], validator, options > value, ...],// 規則2[attribute, validator, options > value, ...],// 規則3...]; }二、規則類型分類 1、核心驗證器&#xff08;內置驗證器&#xff0…

數據結構(三)——棧和隊列

一、棧和隊列的定義和特點 棧&#xff1a;受約束的線性表&#xff0c;只允許棧頂元素入棧和出棧 對棧來說&#xff0c;表尾端稱為棧頂&#xff0c;表頭端稱為棧底&#xff0c;不含元素的空表稱為空棧 先進后出&#xff0c;后進先出 隊列&#xff1a;受約束的線性表&#xff0…

SQL Server 存儲過程開發三層結構規范

以下是《SQL Server 存儲過程開發三層結構規范》的正式文檔結構&#xff0c;適用于企業級數據庫應用開發場景&#xff0c;有助于團隊協作、代碼審查與自動化運維&#xff1a; &#x1f4d8; SQL Server 存儲過程開發三層結構規范 一、架構設計總覽 三層結構簡介 層級命名約定…

接上篇,解決FramePack啟動報錯:“httpx.ReadError: [WinError 10054] 遠程主機強迫關閉了一個現有的連接。“的問題

#工作記錄 FramePack部署&#xff08;從PyCharm解釋器創建和使用開始&#xff09;保姆級教程-CSDN博客 上篇我們記錄到FramePack從克隆到啟動調試的保姆級教程&#xff0c;關于啟動時會報以下錯誤的問題&#xff0c;已作出解決&#xff1a; 報錯摘錄&#xff1a; (.venv) PS F…

ping_test_parallel.sh 并行網絡掃描腳本

并行網絡掃描腳本分析&#xff1a;提高網絡探測效率 引言腳本概述核心代碼分析顏色定義與初始化并行處理機制并行執行與進程控制結果處理與統計 技術亮點性能分析結論附錄&#xff1a;完整腳本 引言 在網絡管理和運維過程中&#xff0c;快速檢測網段內主機的在線狀態是一項常見…

leetcode 3342. 到達最后一個房間的最少時間 II 中等

有一個地窖&#xff0c;地窖中有 n x m 個房間&#xff0c;它們呈網格狀排布。 給你一個大小為 n x m 的二維數組 moveTime &#xff0c;其中 moveTime[i][j] 表示在這個時刻 以后 你才可以 開始 往這個房間 移動 。你在時刻 t 0 時從房間 (0, 0) 出發&#xff0c;每次可以移…

關于vue-office在vue3工程中的引用報錯問題

在vue3項目工程中&#xff0c;根據vue-office文檔在vue2中的引用&#xff1a; //引入VueOfficeDocx組件 相關樣式import VueOfficeDocx from vue-office/docx;import vue-office/docx/lib/index.css; 報錯信息&#xff1a; [plugin:vite:import-analysis] Failed to resolve …

【macOS常用快捷鍵】

以下是 macOS 最常用快捷鍵列表&#xff0c;按使用頻率由高到低分類整理&#xff0c;涵蓋日常操作、效率工具及系統控制&#xff0c;助你快速提升使用效率&#xff1a; 一、基礎高頻操作 快捷鍵功能說明Command C復制選中內容Command V粘貼Command X剪切Command Z撤銷上一…

mdadm 報錯: buffer overflow detected

最近跑 blktest (https://github.com/osandov/blktests) 時發現 md/001 的測試失敗了 單獨執行&#xff0c;最后定位到是 mdadm 命令報錯: buffer overflow detected 這個 bug 目前已經修復: https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git/commit/?id827e1870f3205…