LRU、LFU

算法

🌙 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
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

🌙 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
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