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.
Follow up:
Could you do both operations in O(1) time complexity?
Could you do both operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2 /* capacity */ ); 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 idea is to have a doubly linked list to cache elements. The most recent element will be put into the tail.
We also maintains a HashMap to mapping a key to each element.
In a get operation, we try to find the node in HashMap and put it to the tail.
In a put operation, if the node exists, we put it to the tail and update its value. If it does not exist, we create a new node and put it to the tail. In the meantime, we check if the cache is full.
If the cache is full, we need to remove the mapping of the head in the linked list.
We use a dummy head and a dummy tail for convenience.
Code:
public class LRUCache { class ListNode { int key; int value; ListNode prev; ListNode next; public ListNode(int key, int value) { this.key = key; this.value = value; ListNode prev = null; ListNode next = null; } } private int capacity; private HashMap<Integer, ListNode> map; private ListNode head; private ListNode tail; public LRUCache(int capacity) { this.capacity = capacity; head = new ListNode(-1, -1); tail = new ListNode(-1, -1); head.next = tail; tail.prev = head; map = new HashMap<>(); } public int get(int key) { if (!map.containsKey(key)) { return -1; } ListNode node = map.get(key); node.prev.next = node.next; node.next.prev = node.prev; moveToTail(node); return node.value; } public void put(int key, int value) { if (get(key) != -1) { map.get(key).value = value; return; } if (map.size() == this.capacity) { map.remove(head.next.key); head.next = head.next.next; head.next.prev = head; } ListNode node = new ListNode(key, value); map.put(key, node); moveToTail(node); } public void moveToTail(ListNode node) { node.prev = tail.prev; tail.prev.next = node; tail.prev = node; node.next = tail; } } /** * 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); */