🌙 LRU (opens new window)
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity)
以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key)
如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。void put(int key, int value)
如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 105
最多调用 2 * 105 次 get 和 put
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class DoubleLinkNode {
public key: number
public val: number
public next: DoubleLinkNode | null
public prev: DoubleLinkNode | null
constructor(key: number, val: number) {
this.key = key
this.val = val
this.next = null
this.prev = null
}
}
class LRUCache {
private capacity: number
private cache: Map<any, any>
private dummy: DoubleLinkNode
constructor(capacity: number) {
// 容量
this.capacity = capacity
// 缓存 key, node
this.cache = new Map()
// 哨兵节点
this.dummy = new DoubleLinkNode(-1, -1)
this.dummy.next = this.dummy
this.dummy.prev = this.dummy
}
get(key: number): number {
const node = this.getNode(key)
return node? node.val : -1
}
put(key: number, value: number): void {
// 存在就更新
let node = this.getNode(key)
if(node) {
node.val = value
return
}
// 不存在
node = new DoubleLinkNode(key, value)
// 缓存一下
this.cache.set(key, node)
// 放到头部
this.pushFront(node)
// 判断容量
if(this.cache.size > this.capacity) {
// 删除尾部
this.deleteTail()
}
}
getNode(key: number) {
// 不存在
if(!this.cache.has(key)) {
return null
}
// 存在
const node = this.cache.get(key)
// 删除
this.delete(node)
// 放到头部
this.pushFront(node)
return node
}
delete(node:DoubleLinkNode) {
const prev = node.prev
const next = node.next
prev!.next = next
next!.prev = prev
}
// dummy --> head
// head <--- dummy
// 将node插入到dummy后面
pushFront(node:DoubleLinkNode) {
const head = this.dummy.next
node.prev = this.dummy
node.next = head
head!.prev = node
this.dummy.next = node
}
deleteTail() {
const tail = this.dummy.prev
this.delete(tail!)
this.cache.delete(tail!.key)
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/
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
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
🌙 LFU (opens new window)
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache 类:
LFUCache(int capacity)
- 用数据结构的容量 capacity 初始化对象int get(int key)
- 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。void put(int key, int value)
- 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。
为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
示例:
输入:
["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
输出:
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]
解释:
// cnt(x) = 键 x 的使用计数
// cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
LFUCache lfu = new LFUCache(2);
lfu.put(1, 1); // cache=[1,_], cnt(1)=1
lfu.put(2, 2); // cache=[2,1], cnt(2)=1, cnt(1)=1
lfu.get(1); // 返回 1
// cache=[1,2], cnt(2)=1, cnt(1)=2
lfu.put(3, 3); // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
// cache=[3,1], cnt(3)=1, cnt(1)=2
lfu.get(2); // 返回 -1(未找到)
lfu.get(3); // 返回 3
// cache=[3,1], cnt(3)=2, cnt(1)=2
lfu.put(4, 4); // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
// cache=[4,3], cnt(4)=1, cnt(3)=2
lfu.get(1); // 返回 -1(未找到)
lfu.get(3); // 返回 3
// cache=[3,4], cnt(4)=1, cnt(3)=3
lfu.get(4); // 返回 4
// cache=[3,4], cnt(4)=2, cnt(3)=3
提示:
1 <= capacity <= 104
0 <= key <= 105
0 <= value <= 109
最多调用 2 * 105 次 get 和 put 方法
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
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
class DoubleLinkedNode {
public key: number
public val: number
public frq: number
public next: DoubleLinkedNode | null
public prev: DoubleLinkedNode | null
constructor(key: number, val: number) {
this.key = key
this.val = val
this.frq = 1 // 使用频率
this.next = null
this.prev = null
}
}
class LFUCache {
public capacity: number
public cache: Map<any, any>
public counter: Map<any, any>
public minFrq: number;
constructor(capacity: number) {
// 容量
this.capacity = capacity
// 缓存
this.cache = new Map()
// 使用频率缓存 key: 频率 value: DoubleLinkedList
this.counter = new Map()
// 使用最少的频率
this.minFrq = 0
}
get(key: number): number {
const node = this.getNode(key)
return node ? node.val : -1
}
put(key: number, value: number): void {
let node = this.getNode(key)
if(node) {
// 存在
node.val = value
return
}
// 判断容量
if(this.cache.size === this.capacity) {
// 删除
this.deleteMinFrq()
}
// 创建
node = new DoubleLinkedNode(key, value)
this.minFrq = 1
// 插入缓存
this.insert(node)
this.cache.set(key, node)
}
getNode(key: number) {
if(!this.cache.has(key)) {
return null
}
const node = this.cache.get(key)
const frq = node.frq
// 删除
this.delete(node)
const dummy = this.counter.get(frq)
// 维护min
if(dummy.prev === dummy) {
// 移除空链表
this.counter.delete(frq)
if(this.minFrq === frq) {
this.minFrq++
}
}
// 更新frq并插入
node.frq++
this.insert(node)
return node
}
delete(node: DoubleLinkedNode) {
const prev = node.prev
const next = node.next
prev!.next = next
next!.prev = prev
node.next = null
node.prev = null
}
newList(): DoubleLinkedNode {
const dummy = new DoubleLinkedNode(-1, -1)
dummy.next = dummy
dummy.prev = dummy
return dummy
}
// 插入到头部
// 1: dummy --> a --> b
// 2: dummy --> c --> d
insert(node: DoubleLinkedNode): void {
const frq = node.frq
let dummy = this.counter.get(frq)
if(!dummy) {
dummy = this.newList()
this.counter.set(frq, dummy)
}
const head = dummy.next
node.prev = dummy
node.next = head
head.prev = node
dummy.next = node
}
deleteMinFrq() {
const dummy = this.counter.get(this.minFrq)
const tail = dummy.prev
this.delete(tail)
this.cache.delete(tail.key)
// 移除空链表
if(dummy.prev === dummy) {
this.counter.delete(this.minFrq)
}
}
}
/**
* Your LFUCache object will be instantiated and called as such:
* var obj = new LFUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/
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
124
125
126
127
128
129
130
131
132
133
134
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
124
125
126
127
128
129
130
131
132
133
134