[Leedcode][JAVA][第146题][LRU][哈希表][双向链表]
生活随笔
收集整理的這篇文章主要介紹了
[Leedcode][JAVA][第146题][LRU][哈希表][双向链表]
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【問題描述】
LFU 運用你所掌握的數據結構,設計和實現一個? LRU (最近最少使用) 緩存機制。它應該支持以下操作: 獲取數據 get 和 寫入數據 put 。獲取數據 get(key) - 如果密鑰 (key) 存在于緩存中,則獲取密鑰的值(總是正數),否則返回 -1。 寫入數據 put(key, value) - 如果密鑰不存在,則寫入其數據值。當緩存容量達到上限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而為新的數據值留出空間。進階:你是否可以在?O(1) 時間復雜度內完成這兩種操作?示例:LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );cache.put(1, 1); cache.put(2, 2); cache.get(1); // 返回 1 cache.put(3, 3); // 該操作會使得密鑰 2 作廢 cache.get(2); // 返回 -1 (未找到) cache.put(4, 4); // 該操作會使得密鑰 1 作廢 cache.get(1); // 返回 -1 (未找到) cache.get(3); // 返回 3 cache.get(4); // 返回 4【解答思路】
1. 緩存是有限的,在緩存滿的時候,刪除哪些元素,就有不同的緩存刪除策略。
LRU (Least Recently Used)緩存機制
- 在緩存滿的時候,刪除緩存里最久未使用的數據,然后再放入新元素;
- 數據的訪問時間很重要,訪問時間距離現在最近,就最不容易被刪除。
時間復雜度:O(1) 空間復雜度:O(N)
import java.util.Map;public class LRUCache {private Map<Integer, ListNode> map;/*** 雙鏈表結點類*/private class ListNode {private Integer key;private Integer value;/*** 前驅結點 precursor*/private ListNode pre;/*** 后繼結點 successor(寫成 next 是照顧單鏈表的表示習慣)*/private ListNode next;public ListNode() {}public ListNode(Integer key, Integer value) {this.key = key;this.value = value;}}private int capacity;/*** 虛擬頭結點沒有前驅*/private ListNode dummyHead;/*** 虛擬尾結點沒有后繼*/private ListNode dummyTail;public LRUCache(int capacity) {map = new HashMap<>(capacity);this.capacity = capacity;dummyHead = new ListNode(-1, -1);dummyTail = new ListNode(-1, -1);// 初始化鏈表為 head <-> taildummyHead.next = dummyTail;dummyTail.pre = dummyHead;}/*** 如果存在,把當前結點移動到雙向鏈表的頭部** @param key* @return*/public int get(int key) {if (map.containsKey(key)) {ListNode node = map.get(key);int val = node.value;// 把當前 node 移動到雙向鏈表的頭部moveNode2Head(key);return val;} else {return -1;}}/*** 如果哈希表的容量滿了,就要刪除一個鏈表末尾元素,然后在鏈表頭部插入新元素** @param key* @param value*/public void put(int key, int value) {if (map.containsKey(key)) {// 1、更新 valuemap.get(key).value = value;// 2、把當前 node 移動到雙向鏈表的頭部moveNode2Head(key);return;}// 放元素的操作是一樣的if (map.size() == capacity) {// 如果滿了ListNode oldTail = removeTail();// 設計 key 就是為了在這里刪除map.remove(oldTail.key);}// 然后添加元素ListNode newNode = new ListNode(key, value);map.put(key, newNode);addNode2Head(newNode);}// 為了突出主干邏輯,下面是 3 個公用的方法/*** 刪除雙鏈表尾部結點*/private ListNode removeTail() {ListNode oldTail = dummyTail.pre;ListNode newTail = oldTail.pre;// 兩側結點建立連接newTail.next = dummyTail;dummyTail.pre = newTail;// 釋放引用oldTail.pre = null;oldTail.next = null;return oldTail;}/*** 把當前 key 指向的結點移到雙向鏈表的頭部** @param key*/private void moveNode2Head(int key) {// 1、先把 node 拿出來ListNode node = map.get(key);// 2、原來 node 的前驅和后繼接上node.pre.next = node.next;node.next.pre = node.pre;// 3、再把 node 放在末尾addNode2Head(node);}/*** 在雙鏈表的頭部新增一個結點** @param newNode*/private void addNode2Head(ListNode newNode) {// 1、當前頭結點ListNode oldHead = dummyHead.next;// 2、末尾結點的后繼指向新結點oldHead.pre = newNode;// 3、設置新結點的前驅和后繼newNode.pre = dummyHead;newNode.next = oldHead;// 4、更改虛擬頭結點的后繼結點dummyHead.next = newNode;}public static void main(String[] args) {LRUCache cache = new LRUCache(2);cache.put(1, 1);cache.put(2, 2);System.out.println(cache.map.keySet());int res1 = cache.get(1);System.out.println(res1);cache.put(3, 3);int res2 = cache.get(2);System.out.println(res2);int res3 = cache.get(3);System.out.println(res3);cache.put(4, 4);System.out.println(cache.map.keySet());int res4 = cache.get(1);System.out.println(res4);int res5 = cache.get(3);System.out.println(res5);int res6 = cache.get(4);System.out.println(res6);} }作者:liweiwei1419 鏈接:https://leetcode-cn.com/problems/lru-cache/solution/ha-xi-biao-shuang-xiang-lian-biao-java-by-liweiw-2/【總結】
1.緩存刪除機制
- LRU (Least Recently Used)緩存機制[第146題]
- LFU (Least Frequently Used,最不經常使用)緩存機制[第460
題]
時間空間tradeoff
存取數據時間性能最好 - 哈希表
訪問某個數據,時間優先級得提前,刪除末尾結點的需求,頭尾訪問數據最快 - 雙向鏈表
LinkedList 雙向鏈表 本題需要定制化操作
參考鏈接:https://leetcode-cn.com/problems/lru-cache/solution/ha-xi-biao-shuang-xiang-lian-biao-java-by-liweiw-2/
總結
以上是生活随笔為你收集整理的[Leedcode][JAVA][第146题][LRU][哈希表][双向链表]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 定编定岗定员方案_定岗、定编、定员实施方
- 下一篇: 线性表的顺序存储结构之顺序表类的实现_J