Reference: LeetCode
Difficulty: Medium
Problem
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations:
getandput.
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.
Example:
1 | LRUCache cache = new LRUCache(2); |
Follow up: Could you do both operations in O(1) time complexity?
Analysis
Use LinkedHashMap
Reference: Java LinkedHashMap工作原理及实现
From official documentation:
Hash tableandlinked listimplementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains adoubly-linked listrunning through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order).
Inside:
In HashMap, there are three callback methods that allow LinkedHashMap post-actions.
1 | void afterNodeAccess(Node<K,V> p) { } |
removeEldestEntry is a method that we can override. It is usually invoked after put operation. If it returns true, the first entry would be deleted; otherwise, nothing happens.
Then we can see how put and get work. In LinkedHashMap, put is not re-implemented, but all new features are implemented in afterNodeAccess and afterNodeInsertion callbacks.
1 | // `get` is re-implemented |
accessOrder:
accessOrder is a boolean that indicates two modes of structural modification, which are access-ordered and insertion-ordered modes.
A structural modification is any operation that adds or deletes one or more mappings or, in the case of
access-orderedlinked hash maps, affects iteration order. Ininsertion-orderedlinked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. Inaccess-orderedlinked hash maps, merely querying the map with get is a structural modification.
In other words, when accessOrder is set as true, calls of put, get, and so on would trigger affection on iteration order. The accessed entry will be put at the end of the linked list.
Basic Usage:
accessOrder is false:
1 | // Default mode is insertion-order mode |
accessOrder is true:
1 | // access-ordered mode |
Code for this problem:
We can make the class LRUCache extend from LinkedHashMap. get and put are overloaded, and removeEldestEntry is overridden. It means that we would delete the first entry when this.size() > capacity.
Note: capacity member is a fixed value, while the capacity passed into LinkedHashMap is for speculative purpose (just guessing? optimization?).
1 | class LRUCache extends LinkedHashMap<Integer, Integer> { |
The second approach is similar to the object adapter pattern. A LinkedListMap object is wrapped inside LRUCache.
Note: See comments below.
1 | class LRUCache { |
Time: $O(1)$
Space: $O(capacity)$
Do It By Yourself
欣赏一下别人的代码。我稍微改了一下。自己写了好久还是有 bug,链表有毒。
1 | public class LRUCache { |
Time: $O(1)$
Space: $O(capacity)$