Leetcode 146 LRU緩存 的三種解法

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

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多調用?2 * 105?次?get?和?put

解法1:Map + ArrayList (key)

用一個Map來存放key和value,一個ArrayList來存放訪問順序。
當用戶get、put的key存在時,則從ArrayList中找到對應的key刪除,然后把新訪問的key放到尾部。

頭部表示最久未訪問的數據,尾部表示最新訪問的數據。與數據結構匹配。

class LRUCache {private int capacity;private Map<Integer,Integer> cache;private List<Integer> delete;public LRUCache(int capacity) {this.cache = new HashMap<>(capacity);this.capacity = capacity;this.delete = new ArrayList<>(capacity);}public int get(int key) {Integer value = cache.get(key);if(value != null){// update to delete queuedelete.remove((Integer)key);delete.add((Integer)key);return value;}return -1;}public void put(int key, int value) {// if existedif (cache.containsKey(key)) {// update to delete queuedelete.remove((Integer)key);delete.add((Integer)key);cache.put(key, value);return;}// if cache is full, need to deleteif(cache.size() == capacity){Integer deleteKey = delete.remove(0);cache.remove(deleteKey);}cache.put(key, value);delete.add(key);}}/*** 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);*/

?

性能不怎么好,花了883ms。大部分時間花在ArrayList中查找key的位置和刪除key后移動元素上了。

那我們想辦法改進下,查找和刪除。

解法2:Map + ArrayList (key, timestamp)

我們看到解法1中大部分時間都是在ArrayList中查找key和刪除后移動數據。那么我們能不能不移動數據?答案是可以的。

方法是:我們每次操作key后為其引入一個timestamp,需要刪除這個key時,比較待刪除的timestamp是否沒有更新過,沒更新過則刪除。這里使用程序啟動后的納秒,防止在同一個微秒內完成了多個操作,不能區分。

class LRUCache {private int capacity;private Map<Integer,KeyTime> cache;private List<KeyTime> delete;public LRUCache(int capacity) {this.capacity = capacity;this.cache = new HashMap<>(capacity*3/2);this.delete = new LinkedList<>();}public int get(int key) {KeyTime kt = cache.get(key);if(kt != null){long time = System.nanoTime();kt.time = time;// update to delete queuedelete.add(new KeyTime(key, kt.value, time));return kt.value;}return -1;}public void put(int key, int value) {long time = System.nanoTime();// if existedKeyTime kt = cache.get(key);if (kt != null) {kt.time = time;kt.value = value;// update to delete queuedelete.add(new KeyTime(key, value, time));return;}// if cache is full, need to deleteif(cache.size() == capacity){while(true) {KeyTime deleteKt = delete.remove(0);KeyTime existKt = cache.get(deleteKt.key);if (existKt != null && existKt.time == deleteKt.time) {cache.remove(deleteKt.key);break;}}}cache.put(key, new KeyTime(key, value, time));delete.add(new KeyTime(key, value, time));}static class KeyTime {int key;int value;long time;public KeyTime(int key, int value, long time) {this.key = key;this.value = value;this.time = time;}}}/*** 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);*/

性能已經從883ms提升到了52ms,歷史性的飛躍。還不錯,可惜才28.88%,要是還想進一步優化的話,可以參考官方的LinkedHashMap實現。
?

解法3:HashMap + 雙向鏈表 = LinkedHashMap

這里get/put 后把新訪問的節點移動到表尾處,表頭存放的是最久未訪問的數據。

具體實現如下:
?

public class LRUCache {/*** use double direction linked list to store the order which to evict over due key.*/private int capacity;private Map<Integer,Node> cache;// store the first node to delete which is unused for a long timeprivate Node head;// store the used node recentlyprivate Node tail;public LRUCache(int capacity) {this.capacity = capacity;this.cache = new HashMap<>(capacity);this.head = new Node(-1, 0, null, null);this.tail = new Node(-2, 0, head, null);this.head.next = tail;}public int get(int key) {Node node = cache.get(key);if (node != null) {// existed then move to headmoveToTail(node);return node.value;}return -1;}public void put(int key, int value) {Node node = cache.get(key);if (node != null) {// existed, only need to update positionmoveToTail(node);node.value = value;return;}// cache is full, get the last unused node to removeif (cache.size() == capacity) {// remove the head node which is unused for a long timeNode toDelete = head.next;deleteNode(toDelete);cache.remove(toDelete.key);}node = new Node(key, value, null, null);cache.put(key, node);addToTail(node);}static class Node {int key;int value;Node previous;Node next;public Node(int key, int value, Node previous, Node next) {this.key = key;this.value = value;this.previous = previous;this.next = next;}}public void moveToTail(Node node) {deleteNode(node);addToTail(node);}/*** delete node from list.*/public void deleteNode(Node node) {node.previous.next = node.next;node.next.previous = node.previous;}/*** add node to list tail.*/public void addToTail(Node node) {node.next = tail;node.previous = tail.previous;tail.previous.next = node;tail.previous = node;}  
}

運行時間43ms,超越98.98%,可以了。?

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

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

相關文章

尚硅谷 java 學習Day19 抽象類與抽象方法、接口、內部類

6-5 抽象類(abstract)與抽象方法&#xff08;important&#xff09; 一、什么叫抽象類&#xff1a; 有時候將一個父類設計的非常抽象&#xff0c;以至于它沒有具體的實例&#xff0c;這樣的類稱為抽象類 abstract關鍵字的使用&#xff1a; ? 1、abstract:抽象的 ? 2、abs…

【LeetCode Hot100 鏈表(上)】相交鏈表、反轉鏈表、回文鏈表、環形鏈表、合并兩個有序鏈表、兩數相加

鏈表 1. 相交鏈表問題描述解決思路代碼實現 2. 反轉鏈表問題描述解決思路代碼實現 3. 回文鏈表問題描述解決思路代碼實現 4. 環形鏈表問題描述解決思路代碼實現 5. 環形鏈表II問題描述解決思路代碼實現 6. 合并兩個有序鏈表問題描述解決思路代碼實現 7. 兩數相加問題描述解決思…

【Python pro】基本數據類型

一、數字類型 1.1 數字類型的組成 1.1.1 整數 &#xff08;1&#xff09;十進制&#xff0c;二進制0b&#xff0c;八進制0o&#xff0c;十六進制0x print(16 0b10000 0o20 0x10) # 輸出&#xff1a;True&#xff08;2&#xff09;十進制轉其他進制 a bin(16) b oct(1…

拯救者電腦在重裝系統之后電源計劃丟失Fn+Q切換不了模式怎么恢復?

參考聯想知識庫的一下鏈接&#xff1a; https://iknow.lenovo.com.cn/detail/196192 其中下載的解壓文件后的文件需要復制粘貼到D盤的根目錄下&#xff0c;再來運行文件。若在生成的log文件中看到導入成功以及控制面板中看到已添加的電源計劃即可 如果還是無效可是試試以下的…

ubuntu 執行 sudo apt-get update 報錯

記錄一下&#xff0c;遇到這個問題了&#xff0c;網絡上看到的解決辦法&#xff0c;親測有效 執行sudo apt-get update ,卻報以下錯誤&#xff0c;“SECURITY: URL redirect target contains control characters rejecting ” 經檢查發現&#xff0c;/etc/apt/source.list 下的…

深度集成DeepSeek大模型:WebSocket流式聊天實現

目錄 5分鐘快速接入DeepSeek大模型&#xff1a;WebSocket實時聊天指南創建應用開發后端代碼 (Python/Node.js)結語 5分鐘快速接入DeepSeek大模型&#xff1a;WebSocket實時聊天指南 創建應用 訪問DeepSeek官網 前往 DeepSeek官網。如果還沒有賬號&#xff0c;需要先注冊一個。…

java斷點調試(debug)

在開發中&#xff0c;新手程序員在查找錯誤時, 這時老程序員就會溫馨提示&#xff0c;可以用斷點調試&#xff0c;一步一步的看源碼執行的過程&#xff0c;從而發現錯誤所在。 重要提示: 斷點調試過程是運行狀態&#xff0c;是以對象的運行類型來執行的 斷點調試介紹 斷點調試是…

軟件技術實訓室解決方案(2025年最新版)

軟件產業作為新興產業的核心組成部分&#xff0c;是推動數字經濟發展的重要力量。在“十四五”規劃的新機遇與挑戰下&#xff0c;我國已明確將加強關鍵數字技術創新應用作為戰略重點&#xff0c;并將軟件和信息技術服務業的發展列為重中之重。這不僅是為了加速構建現代產業體系…

foobar2000設置DSP使用教程及軟件推薦

foobar2000安卓中文版&#xff1a;一款高品質手機音頻播放器 foobar2000安卓中文版是一款備受好評的高品質手機音頻播放器。 幾乎支持所有的音頻格式&#xff0c;包括 MP3、MP4、AAC、CD 音頻等。不論是經典老歌還是最新的流行音樂&#xff0c;foobar2000都能完美播放。除此之…

DeepSeek企業級部署實戰指南:從服務器選型到Dify私有化落地

對于個人開發者或嘗鮮者而言&#xff0c;本地想要部署 DeepSeek 有很多種方案&#xff0c;但是一旦涉及到企業級部署&#xff0c;則步驟將會繁瑣很多。 比如我們的第一步就需要先根據實際業務場景評估出我們到底需要部署什么規格的模型&#xff0c;以及我們所要部署的模型&…

I2C、SPI、UART

I2C&#xff1a;串口通信&#xff0c;同步&#xff0c;半雙工&#xff0c;雙線&#xff08;數據線SDA時鐘線SCL&#xff09;&#xff0c;最大距離1米到幾米 SPI&#xff08;串行外設接口&#xff09;&#xff1a;串口通信&#xff0c;同步&#xff0c;全雙工&#xff0c;四線&…

uniapp 連接mqtt

1&#xff1a;下載插件 npm install mqtt 2&#xff1a;創建 mqtt.js /* main.js 項目主入口注入實例 */ // import mqttTool from ./lib/mqttTool.js // Vue.prototype.$mqttTool mqttTool/* 使用范例見 /pages/index/index.vue */ // mqtt協議&#xff1a;H5使用ws/wss APP-…

shell腳本備份PostgreSQL數據庫和庫下表

注意&#xff1a; 以下為對PostgreSQL13.16版本數據庫備份shell腳本參考請確認備份節點上psql和pgdump的版本不至于太低&#xff0c;建議>13.16該腳本目前是對于整庫、&#xff08;默認針對public這個schema&#xff0c;如果有其他schema&#xff0c;請自行添加一層循環&am…

EXCEL解決IF函數“您已為此函數輸入太多個參數”的報錯

IF函數的基本結構是IF(條件, 值為真時的結果, 值為假時的結果)&#xff0c;所以標準的IF函數最多只能有三個參數。當用戶輸入的參數超過三個時&#xff0c;Excel就會報這個錯誤。比如多個IF語句疊加&#xff0c;但可能在嵌套的過程中沒有正確關閉每個IF函數的括號&#xff0c;導…

圖像質量評價指標-UCIQE-UIQM

一、評價指標UCIQE 在文章《An underwater color image quality evaluation metric》中&#xff0c;提到的了評價指標UCIQE&#xff08;Underwater Colour Image Quality Evaluation&#xff09;&#xff0c;是一種無參考圖像質量評價指標&#xff0c;主要用于評估水下圖像的質…

Vue 前端開發中的路由知識:從入門到精通

文章目錄 引言1. Vue Router 簡介1.1 安裝 Vue Router1.2 配置 Vue Router1.3 在 Vue 實例中使用 Vue Router 2. 路由的基本用法2.1 路由映射2.2 路由視圖2.3 路由鏈接 3. 動態路由3.1 動態路徑參數3.2 訪問動態參數3.3 響應路由參數的變化 4. 嵌套路由4.1 定義嵌套路由4.2 渲染…

基于Springboot+微信小程序調用文心一言大模型實現AI聊天

一、文章前言 此文主要實現基于Springboot微信小程序調用文心一言大模型實現AI聊天對話功能&#xff0c;使用Java作為后端語言進行支持&#xff0c;界面友好&#xff0c;開發簡單。 二、開發流程及工具準備 2.1、登錄百度智能云平臺&#xff0c;獲取 API Key 和 Secret Key兩個…

leaflet前端初始化項目

1、通過npm安裝leaflet包&#xff0c;或者直接在項目中引入leaflet.js庫文件。 npm 安裝&#xff1a;npm i leaflet 如果在index.html中引入leaflet.js,在項目中可以直接使用變量L. 注意:盡量要么使用npm包&#xff0c;要么使用leaflet.js庫&#xff0c;兩者一起使用容易發生…

Deepseek官網接口文檔

API 接口 生成完成 生成聊天完成 創建模型 列出本地模型 顯示模型信息 復制模型 刪除模型 拉取模型 推送模型 生成嵌入 列出運行中的模型 版本 約定 模型名稱 模型名稱遵循 model:tag 格式&#xff0c;其中 model 可以有一個可選的命名空間&#xff0c;例如 ex…

容器運行常見數據庫

一.涉及鏡像壓縮包 均為amd架構版本&#xff1a;mysql:5.7.42、postgres:13.16、dm8:20250206_rev257733_x86_rh6_64、oceanbase-ce:v4.0、opengauss:5.0.2 通過網盤分享的文件&#xff1a;db.tgz 鏈接: https://pan.baidu.com/s/1EBbFPZj1FxCA4_GxjVunWg?pwd563s 提取碼: 5…