题目:
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?
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</pre>
分析:
用2个hashmap
第1个hashmap keyValDict存储key -> value and index(think of it as last access time)
第1个hashmap evictKeyDict存储index -> key
实现get:
返回keyValDict.get(key),同时更新这个key的index = new index(new index is greater than any index)
实现put:
如果key exist, 修改key对应的value,并且更新key的index = new index(new index is greater than any index)
如果key not exist 那么插入新的元素,在插入之前先检查一下capacity
如果capacity未满,用keyValDict.put(key, {value, new index}), evictKeyDict.put(new index, key)插入。这里我们同时插入了两个信息,一是key,value,二是这个key的index(last access time)
如果capacity已满,删除last_evict_index所指向的evictKeyDict的entry。同时删除对应在keyValDict的entry。更新size。
时间复杂度是O(1)因为两个函数都只是操作了hashmap
class LRUCache {
class IntegerPair{
public int val;
public int index;
public IntegerPair(int v, int i) {val = v; index = i;}
}
private int capacity, size, last_evict_index, assign_new_index;
private HashMap<Integer, IntegerPair> keyValDict = new HashMap<Integer, IntegerPair>();
private HashMap<Integer, Integer> evictKeyDict = new HashMap<Integer, Integer>();
public LRUCache(int capacity) {
this.capacity = capacity;
size = assign_new_index = 0;
this.last_evict_index = -1;
}
public int get(int key) {
IntegerPair ret = keyValDict.get(key);
if(ret != null){
int index = assign_new_index++;
IntegerPair pair = new IntegerPair(ret.val, index);
keyValDict.put(key, pair);
evictKeyDict.remove(ret.index);
evictKeyDict.put(index, key);
return ret.val;
}
return -1;
}
public void put(int key, int value) {
// If key already exists, just update
IntegerPair ret = keyValDict.get(key);
if(ret != null) {
int index = assign_new_index++;
IntegerPair pair = new IntegerPair(value, index);
keyValDict.put(key, pair);
evictKeyDict.remove(ret.index);
evictKeyDict.put(index, key);
return;
}
// If key not exist, insert, Check capacity first
if(this.size == this.capacity) {
Integer evictKey = null;
this.last_evict_index++;
while(last_evict_index < assign_new_index) {
evictKey = evictKeyDict.get(last_evict_index);
if(evictKey != null)
break;
this.last_evict_index++;
}
keyValDict.remove(evictKey);
evictKeyDict.remove(this.last_evict_index);
}
// Now, insert
int index = assign_new_index++;
IntegerPair pair = new IntegerPair(value, index);
keyValDict.put(key, pair);
evictKeyDict.put(index, key);
if(size < capacity)
size++;
}
}
后来看了一下网络上的解答,普遍是用以下思路
用一个hashmap,每个key指向一个double linked list node。Node里包含了value,next,prev等信息。每当一个元素被access/modify之后就把该node移动到头部。如果要evict某个元素的话,直接移除double linked list的尾部就好了。
时间复杂度也是O(1), 因为hashmap的操作复杂度是O(1), 移动链表的node到头部,删除尾部node等操作也都是O(1)
import java.util.*;
class LRUCache {
/*
Data structure:
Use hash map to store key -> node containing value
When putting new element or accessing an element, we should put it to the front of list
When reaching capacity, always remove the tail of list
*/
public class DoublyListNode {
int key;
int val;
DoublyListNode prev;
DoublyListNode next;
DoublyListNode(int key, int val) { this.key = key; this.val = val;}
}
// Current size of the list
int size;
// Current capacity of the list
int capacity;
// Doubly LinkedList for O(1) insert/remove
DoublyListNode list_head;
// Hashmap for O(1) access
Map<Integer, DoublyListNode> map;
public LRUCache(int capacity) {
size = 0;
this.capacity = capacity;
list_head = new DoublyListNode(0, 0);
DoublyListNode list_tail = new DoublyListNode(0, 0);
list_head.next = list_tail;
list_head.prev = list_tail;
list_tail.next = list_head;
list_tail.prev = list_head;
map = new HashMap<>();
}
public void remove_node(DoublyListNode node) {
DoublyListNode node_prev = node.prev;
DoublyListNode node_next = node.next;
node_prev.next = node.next;
node_next.prev = node_prev;
}
public void insert_node(DoublyListNode node) {
DoublyListNode prev_next = list_head.next;
list_head.next = node;
node.next = prev_next;
node.prev = list_head;
prev_next.prev = node;
}
public int get(int key) {
DoublyListNode node = map.get(key);
if(node == null) return -1;
// Key exists
int val = node.val;
// Remove the node from wherever it was
remove_node(node);
// Insert the node to front
insert_node(node);
return val;
}
public void put(int key, int value) {
DoublyListNode node = map.get(key);
if(node == null) {
node = new DoublyListNode(key, value);
map.put(key, node);
}
else {
node.val = value;
// Remove the node from wherever it was
remove_node(node);
size--;
}
size++;
// Before inserting anthing... check if capacity is full
if(size > capacity) {
// If capacity full, then remove tail
map.remove(list_head.prev.prev.key);
remove_node(list_head.prev.prev);
size--;
}
// Insert the node to front
insert_node(node);
}
}
/**
* 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);
*/