[LeetCode] 146. 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.
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
Thought process:
Use a linked list to store the values and their orders in the cache. Use a hash map to map keys to list node. The linked list's order will be changing constantly. Get operation move the value requested to to head/tail of the list, to represent that it's most recently used. Put operation will sometimes require the tail/head list node to be removed.
To satisfy the above cases, I have two options:
- Use a doubly linked list, and map key directly to its corresponding node.
- Use a singly linked list, and map key to the previous node.
Using a singly-linked list:
- Get: check map.
- Key exists: move value to tail of the list. Return value.
- Key doesn't exist: return -1.
- Put:
- Key exists: set to new value, and move the node to the most recently used direction.
- Key doesn't exist:
- Insert node to the most recently used position.
- If capacity is exceeded, evict the least recently used item (head of list).
Solution 1 (singly-linked list):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | public class LRUCache { private class ListNode { int key; int value; ListNode next; ListNode(int key, int value) { this.key = key; this.value = value; } } private ListNode dummy; private ListNode tail; private Map<Integer, ListNode> map; private int capacity; public LRUCache(int capacity) { dummy = new ListNode(0, 0); tail = dummy; map = new HashMap<>(); this.capacity = capacity; } public int get(int key) { if (map.containsKey(key)) { ListNode previous = map.get(key); moveToTail(previous); return tail.value; } else { return -1; } } public void put(int key, int value) { if (map.containsKey(key)) { ListNode previous = map.get(key); previous.next.value = value; moveToTail(previous); } else { if (map.size() == capacity) { map.remove(dummy.next.key); dummy.next = dummy.next.next; if (dummy.next != null) { map.put(dummy.next.key, dummy); } } ListNode put = new ListNode(key, value); tail.next = put; map.put(key, tail); tail = put; } } private void moveToTail(ListNode previous) { ListNode got = previous.next; if (got == tail) { return; } previous.next = got.next; if (previous.next != null) { map.put(previous.next.key, previous); } tail.next = got; got.next = null; map.put(got.key, tail); tail = got; } } /** * 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); */ |
Solution 2 (doubly-linked list):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | public class LRUCache { class ListNode { int key; int value; ListNode previous; ListNode next; ListNode(int key, int value) { this.key = key; this.value = value; } } private ListNode head; private ListNode tail; private Map<Integer, ListNode> map; private int capacity; private int size; public LRUCache(int capacity) { head = new ListNode(0, 0); tail = new ListNode(0, 0); head.next = tail; tail.previous = head; map = new HashMap<>(); this.capacity = capacity; size = 0; } public int get(int key) { if (!map.containsKey(key)) { return -1; } ListNode node = map.get(key); moveToHead(node); return node.value; } public void put(int key, int value) { if (map.containsKey(key)) { ListNode node = map.get(key); node.value = value; moveToHead(node); } else { if (size == capacity) { evict(); size--; } insert(key, value); } } private void moveToHead(ListNode node) { deleteNode(node); addToHead(node); } private void evict() { ListNode lru = tail.previous; deleteNode(lru); map.remove(lru.key); } private void insert(int key, int value) { ListNode node = new ListNode(key, value); addToHead(node); map.put(key, node); size++; } private void addToHead(ListNode node) { ListNode next = head.next; head.next = node; node.previous = head; node.next = next; next.previous = node; } private void deleteNode(ListNode node) { node.previous.next = node.next; node.next.previous = node.previous; } } /** * 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); */ |
Time complexity: O(1) for both get and put.
Comments
Post a Comment