标准答案

  1. Map 保存 key 到双向链表节点的映射。
  2. get 命中和 put 更新都把节点移到头部;超过容量时删除尾部并从 Map 移除。
  3. 每次操作的时间复杂度 O(1),空间复杂度 O(capacity),边界包括容量为 0 和更新已有 key。

题目解析

LRU 的两个操作分别需要两种能力:Map 负责按 key 找到节点,双向链表负责把节点从当前位置摘下并移动到头部。两者结合后 get、更新和淘汰都不需要扫描。

头部代表最近使用,尾部代表最久未使用。更新已有 key 要保留节点或替换值并移动到头部;超出容量时先从链表删除尾部,再从 Map 删除对应 key。

容量为零、重复 set、未命中和 value 为 undefined 都要有明确语义。操作复杂度 O(1),空间复杂度 O(capacity),前提是链表指针维护始终正确。

代码示例

省略节点类的完整 LRU 核心:

TypeScript
get(key: string) {
  const node = this.map.get(key)
  if (!node) return undefined
  this.remove(node); this.addFirst(node)
  return node.value
}

set(key: string, value: string) {
  const old = this.map.get(key)
  if (old) { old.value = value; this.remove(old); this.addFirst(old); return }
  const node = { key, value, prev: null, next: null }
  this.map.set(key, node); this.addFirst(node)
  if (this.map.size > this.capacity) {
    const last = this.removeLast(); this.map.delete(last.key)
  }
}

常见误区

  • 误区:只用 Map 却声称能按访问顺序 O(1) 淘汰。改正:需要 Map 加双向链表,或使用明确支持该语义的数据结构。
  • 误区:更新已有 key 后没有移动到头部。改正:set 命中也代表最近使用,必须刷新链表顺序。
  • 误区:容量为 0 时仍保留节点。改正:在写入前处理零容量,保证 Map 和链表都不留下条目。

作者信息