1. 引言
題目:[LeetCode]146.LRU緩存機制
運用你所掌握的數據結構,設計和實現一個 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
2. 解決方案分析
其實這道題並不難,就是找一個合適的數據結構去存儲。
每次get之後,要把get到的數提前到最前面,如果沒有get到,則返回-1。
put的時候,先查看有沒有相同的key元素,有的話,直接把那個刪掉,否則不做處理。然後判斷當前的元素個數是否小於capacity,小於的話就在最前面添加新元素即可,否則在最前面添加新元素之後,還要把最後面的元素刪掉。
3. 簡單的實現方式
思路簡單,難的是時間複雜度,我最開始直接想的是利用現成的數據結構,就用的是Java中的LinkedList和HashMap,HashMap中存儲的是key和value,LinkedList中存儲的是若干個map。代碼如下:
package com.darrenchan.dp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
class LRUCache2 {
public int capacity;
public List<Map<Integer, Integer>> list = new LinkedList<>();
public LRUCache2(int capacity) {
this.capacity = capacity;
}
/**
* get方法的算法
*/
public int get(int key) {
int value = -1;
for (Map<Integer, Integer> map : list) {
if(map.get(key) != null){
value = map.get(key);
list.remove(map);
list.add(0, map);
break;
}
}
return value;
}
public void put(int key, int value) {
int index = -1;
for (Map<Integer, Integer> map : list) {
if(map.get(key) != null){
list.remove(map);
break;
}
}
int size = list.size();
Map<Integer, Integer> map = new HashMap<>();
map.put(key, value);
if(size < capacity){
list.add(0, map);
}else{
list.add(0, map);
list.remove(capacity);
}
}
public static void main(String[] args) {
LRUCache2 lruCache = new LRUCache2(2);
System.out.println(lruCache.get(2));
lruCache.put(2, 6);
System.out.println(lruCache.get(1));
lruCache.put(1, 5);
lruCache.put(1, 2);
System.out.println(lruCache.get(1));
System.out.println(lruCache.get(2));
}
}
這樣時間複雜度是O(N),因爲每次需要for循環,時間超時。
3.優化的方案
Java自帶的LinkedHashMap數據結構可以解決我們的問題,使get和put方法都在N(1)複雜度的情況下完成。先直接看代碼吧。
class LRUCache {
private int capacity;
private LinkedHashMap<Integer,Integer> map;
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new LinkedHashMap<>();
}
public int get(int key) {
if(map.containsKey(key)){
int value = map.get(key);
map.remove(key);
map.put(key,value);
return value;
}else{
return -1;
}
}
public void put(int key, int value) {
if(map.containsKey(key)){
map.remove(key);
}
if(map.size() >= capacity){
map.remove(map.keySet().iterator().next());
}
map.put(key,value);
}
}
4. LinkedHashMap數據結構的說明
傳統的HashMap有一個問題,就是迭代HashMap的順序並不是HashMap放置的順序,也就是無序。
HashMap的這一缺點往往會帶來困擾,因爲有些場景,我們期待一個有序的Map。
這個時候,LinkedHashMap就閃亮登場了,它雖然增加了時間和空間上的開銷,但是通過維護一個運行於所有條目的雙向鏈表,LinkedHashMap保證了元素迭代的順序。該迭代順序可以是插入順序或者是訪問順序。
不過注意的是,該循環雙向鏈表的頭部存放的是最久訪問的節點或最先插入的節點,尾部爲最近訪問的或最近插入的節點,迭代器遍歷方向是從鏈表的頭部開始到鏈表尾部結束,在鏈表尾部有一個空的header節點,該節點不存放key-value內容,爲LinkedHashMap類的成員屬性,循環雙向鏈表的入口。
5. 總結
我們要熟練掌握JDK自帶的數據結構的原理,這對提高我們的開發效率是非常有幫助的。
參考