Site Search:

LRU Cache

LRU Cache

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

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(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.

The cache is initialized with a positive capacity.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LRUCache cache = new LRUCache( 2 );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

Solution

The shortcut is to use LinkedHashMap with capacity 2. We also need to override
removeEldestEntry

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity; 
    }

The LinkedHashMap implements the eviction with a double linked list. The hashmap maps the key to a double linked list node. Whenever a node is added, it is added after the head node. If the size is bigger 
than capacity, a node is removed from tail. A get operation remove a linked list node, put it at the head. 

import java.util.*;

class Solution {
  public static void main(String[] args) {
    LRUCache cache = new LRUCache(2);
    cache.put(1, 1);
    cache.put(2, 2);
    System.out.println(cache.get(1));       // returns 1
    cache.put(3, 3);    // evicts key 2
    System.out.println(cache.get(2));       // returns -1 (not found)
    cache.put(4, 4);    // evicts key 1
    System.out.println(cache.get(1));       // returns -1 (not found)
    System.out.println(cache.get(3));       // returns 3
    System.out.println(cache.get(4));       // returns 4
  }
  
  static class LRUCache {
    private int capacity = 0;
    private Node head;
    private Node tail;
    private int size;
    private Map<Integer, Node> map = new HashMap<>();
    public LRUCache(int capacity) {
      this.capacity = capacity;
      head = new Node();
      tail = new Node();
      head.next = tail;
      tail.next = head;
    }
    public int get(Integer key) {
      Node node = map.get(key);
      if(node != null) {
        removeNode(node);
        addNode(node);
        return node.val;
      } else {
        return -1;
      }
    }
    
    public void put(Integer key, Integer val) {
      Node node = new Node();
      node.key = key;
      node.val = val;
      addNode(node);
      size++;
      map.put(key, node);
      if(size > capacity) {
        Node tail = removeTail();
        size--;
        map.remove(tail.key);
      }
    }
    
    private void removeNode(Node node) {
      Node pre = node.pre;
      Node next = node.next;
      pre.next = next;
      next.pre = pre;
    }
    
    // head -> node
    // head -> newNode -> node
    private void addNode(Node node) {
      Node next = head.next;
      node.next = next;
      node.pre = head;
      head.next = node;
      next.pre = node;
    }
    
    // noden -> node -> tail
    private Node removeTail() {
      Node node = tail.pre;
      Node pre = node.pre;
      pre.next = tail;
      tail.pre = pre;
      return node;
    }
    
  }
  
  static class Node {
    int key;
    int val;
    Node pre;
    Node next;
  }  
}
All the operations has cost O(1), the space is O(capacity), the extra space came from the double linked list with at most size equal the capacity.