【Leetcode】:LRU緩存機制引發的思考

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自帶的數據結構的原理,這對提高我們的開發效率是非常有幫助的。

參考

  1. http://www.cnblogs.com/DarrenChan/p/8744354.html
  2. https://www.cnblogs.com/xiaoxi/p/6170590.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章