题目
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥已经存在,则变更其数据值;如果密钥不存在,则插入该组「密钥/数据值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
1
2
3
4
5
6
7
8
9
10
11
|
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
|
题解
题目要求复杂度为O(1),那么肯定要用到hash表,同时数据的存储是有优先级需求的,要求最近访问的在前,较少访问的在后,这样可以快速的将数据删掉。
数据的存储形式最开始考虑是使用vector,原因是认为vector的index可以简单的与hash表进行关联,通过hash表可以快速的定位到vector中的哪个元素。但后来发现当删除元素,或是调整元素顺序时,vector的时间复杂度明显不是O(1),要想快速的插入和删除,那么肯定得用list。但若是用list,该怎么和hash表关联起来?到这里,我算是卡壳了,最终打算先用list实现一个复杂度不是O(1)的代码出来,对应的代码如下:
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
|
class LRUCache {
public:
LRUCache(int capacity):max_count(capacity){}
int get(int key) {
int value = -1;
// 遍历list,如果找到,那么保存值并删除
for (auto itr = l.begin(); itr != l.end(); itr++) {
if (itr->first == key) {
value = itr->second;
l.erase(itr);
break;
}
}
// 如果值不为-1,重新插入到链表头
if (value != -1)
l.push_front({ key, value });
// 返回值
return value;
}
void put(int key, int value) {
int count = 0;
// 遍历链表
for (auto itr = l.begin(); itr != l.end(); itr++, count++) {
// 如果能够从链表中找到key,那么删除
if (itr->first == key) {
l.erase(itr);
break;
}
// 如果链表是最大长度,且已经到了链表的最后一个元素
// 那么删除链表的最后一个元素
if (l.size() == max_count && count == l.size() - 1) {
l.erase(itr);
break;
}
}
// 将元素重新插入到链表头
l.push_front({ key, value });
}
private:
int max_count;
std::list<pair<int, int>> l;
};
|
在上面的实现中,获取数据以及插入数据的时间复杂度都是O(N),提交代码后,当缓存数据很多时,代码运行超时。
最后看了别人的题解,发现还是用链表,但用的是双向链表。而让自己头疼的hash表与链表的关联问题则是自己傻逼了,vector可以存index,链表就不能直接存链表的结点吗?这里的主要原因估计是,自己想的一直是用标准库中的list,其结点对外并不透明,因此也就没有想到这里。
后来试了下,list的迭代器实际上是可以作为节点的,因此hash表中直接存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
|
class LRUCache {
public:
LRUCache(int capacity) :max_count(capacity) {}
int get(int key) {
// 没找到直接返回-1
if (map.find(key) == map.end()) {
return -1;
}
// 找到值并删除该节点
int value = map[key]->second;
l.erase(map[key]);
// 在头部重新插入该节点,并更新map中该key对应的节点
l.push_front({ key, value });
map[key] = l.begin();
return value;
}
void put(int key, int value) {
// 如果找到key,那么删除节点,并放到链表头
if (map.find(key) != map.end()) {
l.erase(map[key]);
l.push_front({ key, value });
}
// 没有找到key
else {
// 且链表已经是最大长度,那么删除链表尾部的节点
// 同时记得删除map中该key对应的节点
if (l.size() == max_count) {
auto itr = l.back();
map.erase(itr.first);
l.pop_back();
}
// 在链表头添加节点
l.push_front({ key, value });
}
// 更新map中该key对应的节点
map[key] = l.begin();
}
private:
int max_count;
// pair<int,int> -> (key, value)
list<pair<int, int>> l;
// map<int, node> -> (key, node),迭代器即为节点
unordered_map<int, list<pair<int, int>>::iterator> map;
};
|