146. LRU 缓存

考点

  • 哈希表与链表的综合

题解

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
class LRUCache {
private:
struct Node {
int key;
int value;
Node(int _key, int _value) : key(_key), value(_value) {}
};
int capacity;
list<Node> l;
unordered_map<int, list<Node>::iterator> m;

public:
LRUCache(int _capacity) : capacity(_capacity) {}

int get(int key) {
auto it = m.find(key);
if (it == m.end()) return -1;
int value = it->second->value;
//先从链表中删除
l.erase(it->second);
//再添加到头部
l.emplace_front(key, value);
//更新哈希表
m[key] = l.begin();
return value;
}

void put(int key, int value) {
auto it = m.find(key);
if (it == m.end()) {
//容量为0就不能插入新元素
if (capacity == 0) return;
if (m.size() == (unsigned long long)capacity) {
m.erase(l.back().key);
l.pop_back();
}
} else {
l.erase(it->second);
}
l.emplace_front(key, value);
m[key] = l.begin();
}
};

思路

LRU又称为最近最少使用页面置换算法(Least Recently Used),淘汰最早访问的页面

题目要求查找与增删节点时均要达到常量时间复杂度,很容易想到哈希表+链表的组合

无序哈希表在平均情况下的查找为常量时间复杂度,而有序哈希表的查找为对数时间复杂度,因为底层是红黑树

所以有以下思路:

  1. 每次访问/更新节点时,先将其从链表中删除,再将其插入到链表头部以代表最近访问

    这样一来链表末尾节点自然是最早访问的了

  2. 因为只涉及到删除链表中节点、删除链表尾部节点和插入链表头部节点,使用双向链表较为方便;这里使用list这一STL库来完成

  3. 使用哈希表来记录键与节点的映射关系,这样就能直接操控链表节点而不用去遍历寻找

数据结构设计如图所示