LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.node

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.數據結構

set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.this


這題須要咱們設計一個cache, 有get和set兩個操做。由於是cache, 全部兩個操做的時間複雜度都必須是O(1)。
get(key) -- O(1) 很明顯,咱們須要用一個hashmap來實現O(1)的操做。
set(key, value) -- O(1) 這裏有兩種狀況,key沒出現過,就直接加在head。這裏出現一個關鍵詞head。
由於使用線性的數據結構,而且表示操做的前後順序,這樣的結構就是鏈表。是單鏈表仍是雙鏈表?下面咱們模擬一下:
capacity = 3
set(1, 100)
set(2, 200)
set(3, 300)
get(2)
若是是單鏈表,簡單表示以下:
1 -> 2 -> 3 -> null
咱們能夠獲得2並放在頭部。可是這裏用的單鏈表,咱們沒法知道2的前面是什麼,2前面的全部點都會脫離總體。因此須要一個雙鏈表。
1 <=> 3 <=> 2 <=> null
咱們繼續操做,set(4, 400),發現已經達到LRU的容量,須要移除,這時候發現咱們須要一個尾部來告訴咱們須要移除哪一個點。
咱們發現,不管是get(key)仍是set(key, value)都有兩個簡單操做組成,從鏈表中移除,放到鏈表頭部。
能夠定義兩個helper function: remove(node), setHead(node)。debug

代碼以下,帶註釋:設計

public class LRUCache {
    class Node{
        int key;
        int value;
        Node pre;       // point to tail direction
        Node next;      // point to head direction
        public Node(int key, int value){
            this.key = key;
            this.value = value;
        }
    }
    
    int capacity;
    Map<Integer, Node> map = new HashMap<>();
    Node tail = null;
    Node head = null;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    
    public int get(int key) {
        if(map.containsKey(key)){       // remove from LRU and put it to head of LRU
            Node n = map.get(key);
            remove(n);
            setHead(n);
            return n.value;
        }
        return -1;
    }
    
    public void set(int key, int value) {
        if(map.containsKey(key)){           // change node value, remove from LRU and put it to head of LRU
            Node old = map.get(key);
            old.value = value;
            remove(old);
            setHead(old);
        } else {
            Node newNode = new Node(key, value);
            if(capacity == map.size()){     //  remove the tail
                map.remove(tail.key);
                remove(tail);
            }
            setHead(newNode);               // set newNode to head
            map.put(key, newNode);
        }
    }
    
    public void remove(Node n){
        if(n.pre != null) {         // change pre node connection
            n.pre.next = n.next;
        } else {                    // check if it is the tail
            tail = n.next;
        }
        
        if(n.next != null) {        // change next node connection
            n.next.pre = n.pre;
        } else {                    // check if it is the head
            head = n.pre;
        }
    }
    
    public void setHead(Node n){
        n.pre = head;
        n.next = null;
        
        if(head != null) {  // check head exist or Not ?
            head.next = n;
        }
        
        head = n;
        if(tail == null){    // empty LRU, intitailize tail node
            tail = head;
        }
    }
}

使用dummyEnd 和dummyHead能夠簡化代碼。code

public class LRUCache {
    
    int capacity;
    Map<Integer, Node> map;
    Node dummyEnd;
    Node dummyHead;
    int count;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.count = 0;
        map = new HashMap<Integer, Node>();
        dummyEnd = new Node(0,0);
        dummyHead = new Node(0,0);
        dummyEnd.next = dummyHead;
        dummyHead.pre = dummyEnd;
    }
    
    public int get(int key) {
        Node node = map.get(key);
        if(node == null) {
            return -1;
        } else {
            remove(node);
            putToHead(node);
            return node.val;
        }
    }
    
    public void put(int key, int value) {
        Node oldNode = map.get(key);
        if(oldNode == null) {
            ++count;
            Node newNode = new Node(key, value);
            map.put(key, newNode);
            putToHead(newNode);
            
            if(count > capacity){
                // 從LRU移除
                // 第一次在這裏debug了很久,要先取出nextNode, 否則map裏remove的就是錯誤的點,即dummy.next.next。
                Node nextNode = dummyEnd.next;
                remove(nextNode);
                // 從map移除
                map.remove(nextNode.key);
                --count;
            }
            
        } else {
            // 改變值,先移除,再放入頭部
            oldNode.val = value;
            remove(oldNode);
            putToHead(oldNode);
        }
    }
    
    public void putToHead(Node node){
        // 加到頭和前一個點的中間
        Node preNode = dummyHead.pre;
        preNode.next = node;
        node.pre = preNode;
        dummyHead.pre = node;
        node.next = dummyHead;
    }
    
    public void remove(Node node){
        // 移除。
        node.next.pre = node.pre;
        node.pre.next = node.next;
        // node 若是從尾部移除,將不會指向任何點。
        node.pre = null;
        node.next = null;
    }
    
    class Node{
        int key, val;
        Node pre, next;
        public Node(int key, int val){
            this.key = key;
            this.val = val;
        }
    }
}

/**
 * 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);
 */
相關文章
相關標籤/搜索