05 August 2008
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations:
and 1
get
.1
put
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.1
get(key)
- 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.1
put(key, value)
Follow up:
Could you do both operations in O(1) time complexity?
Example:
1
2
3
4
5
6
7
8
9
10
11
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
要求实现 Least Recently Used (LRU)的缓存器,这里有公众号“程序员小灰”的对LRU的讲解,非常诙谐,一看就懂,主要是对哈希链表的实现,这里就不多解释了,实现两个方法,get和put, 其中get函数是通过输入key来获得value,如果成功获得,该键值对(key, value)就升至缓存器中最常用的位置(顶部),如果key不存在,则返回-1;put函数是插入一对新的对(key, value),如果原缓存器中有该key,则需要先删除掉原有的,将新的插入到缓存器的顶部。如果不存在,则直接插入到顶部。若加入新的值后缓存器超过了容量,则需要删掉一个最不常用的值,也就是底部的值。要求二者的操作均为O(1)。
具体实现时创建一个hashmap和一个双向链表,保存关键值key和缓存器各项的迭代器之间映射,从而可以以O(1)的时间内找到目标项;另外四个私有变量,capacity是缓存器的容量大小, count是标定目前缓存中键值对个数,和哈希链表的头尾指针。然后再来看get和put如何实现,get相对简单些,我们在hash中查找给定的key,如果存在则将此项移到顶部,并返回value,若不存在返回-1。对于put,查找给定的key,如果存在就删掉原有项,并在顶部插入新来项,然后判断是否溢出,若溢出则删掉底部项(最不常用项)。
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class LRUCache {
/**
* 1. 创建一个内部类Node,封装需要的key-value键值对的对象
*/
class Node {
private int key, value;
private Node prev, next;
public Node (int key, int value) {
this.key = key;
this.value = value;
}
}
/**
* 2. 依靠创建的 Node 类型构建一个双链表内部类,实现几个需要的 API
* 这些操作的时间复杂度均为 O(1),
* 其中,如同单链表,head和tail分别是链表的头节点和尾节点,不在数据内
*/
class DoubleList {
private Node head, tail;
private int size;
public DoubleList() {
// 创建双链表的头尾,头尾不计入双链表的size中
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail; // 双链表的head指向tail
tail.prev = head; // 双链表的tail指向head
size = 0;
}
// 在链表头部添加节点 x,时间 O(1)
public void addFirst(Node x) {
// 在head和第一个node之间插入
x.next = head.next;
x.prev = head;
head.next.prev = x; // 插入前的第一个node
head.next = x; // 插入前的第一个node
size++;
}
// 删除链表中的 x 节点(x 一定存在),由于是双链表且给的是目标 Node 节点,时间 O(1)
public void remove(Node x){
x.prev.next = x.next;
x.next.prev = x.prev;
size--;
}
// 删除链表中最后一个节点,并返回该节点,时间 O(1)
public Node removeLast(){
if (tail.prev == head) { // 双链表目前木有元素
return null;
}
Node last = tail.prev;
remove(last); // 直接实现的调用remove方法移除最后一个元素
return last;
}
// 返回链表长度,时间 O(1)
public int size(){
return size;
}
}
/**
* 3. 创建一个上面创建好的双链表,结合一个新建的HashMap,实现LinkedHashMap的功能
*/
// k-v: key -> Node(key, val)
private HashMap<Integer, Node> map;
// Node(k1, v1) <-> Node(k2, v2)...
private DoubleList cache;
// 最大容量
private int cap;
public LRUCache(int capacity) {
this.cap = capacity;
map = new HashMap<>();
cache = new DoubleList();
}
public int get(int key) {
// 如果key不存在,直接返回-1
if (!map.containsKey(key)) {
return -1;
}
// 否则用put将数据(key, value)提到开头,并返回相应的值
int value = map.get(key).value;
put(key, value);
return value;
}
public synchronized void put(int key, int value) {
// 先把新节点做出来
Node x = new Node(key, value);
// 如果key已存在,把旧的数据删除,把新的数据x插入到开头
if (map.containsKey(key)) {
// 删除旧的节点,新的插到头部
cache.remove(map.get(key));
cache.addFirst(x);
// 更新map中的数据
map.put(key, x);
} else {
// key不存在,先检查是否有空间
if (cap == cache.size()) {
// 删除链表最后一个数据,腾位置
Node last = cache.removeLast(); //设计的removeLast()返回节点
map.remove(last.key);
}
// 把新节点添加到头部,map 中新建 key 对新节点 x 的映射
cache.addFirst(x);
map.put(key, x);
}
}
}
/**
* 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);
*/
如果用Java内置的LinkedHashMap,这个是对HashMap + Double LinkedList进行了封装,不需要自己实现数据结构了,就会简单很多,而且官方API经过各种优化比自己实现的数据结构的速度一般要快,但有时候面试不让用LinkedHashMap。
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
class LRUCache {
private volatile Map<Integer, Integer> map;
private int cacheSize;
public LRUCache(int initSize) {
this.cacheSize = initSize;
// 重写removeEldestEntry()方法,只要没有位置就移除最后一个entry
this.map = new LinkedHashMap<Integer, Integer>(initSize, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > LRUCache.this.cacheSize;
}
};
}
public int get(int inKey) {
if (!map.containsKey(inKey)) {
return -1;
}
int val = map.get(inKey);
// 同样利用put方法将数据提前
put(inKey, val);
return val;
}
public synchronized void put(int key, int value) {
// 直接利用Java中LinkedHashMap实现的put方法
map.put(key, value);
}
}
/**
* 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);
*/