编码母题 / P0 / 经典算法 / 05

最近最少使用缓存

重要程度必练难度中等预计完成40–50 分钟

题目是什么

实现 LRUCache(capacity),capacity 为正整数;put(key,value) 插入或更新,get(key) 返回值、未命中返回 -1。读命中与更新都刷新最近使用顺序;超容量时淘汰最久未使用项,两操作平均 O(1)。

输入输出示例

容量 2,依次 put(1,1)、put(2,2)、get(1)、put(3,3),再 get(2) 返回 -1,因为键 2 最久未使用。

练习起手式与验证

/*
 * 题目:最近最少使用缓存
 * 要求:实现 LRUCache(capacity),capacity 为正整数;put(key,value) 插入或更新,get(key) 返回值、未命中返回 -1。读命中与更新都刷新最近使用顺序;超容量时淘汰最久未使用项,两操作平均 O(1)。
 * 示例:容量 2,依次 put(1,1)、put(2,2)、get(1)、put(3,3),再 get(2) 返回 -1,因为键 2 最久未使用。
 */
class LRUCache {
  constructor(capacity) {}
  get(key) {}
  put(key, value) {}
}
const cache = new LRUCache(2);
cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1)); // 预期:1
cache.put(3, 3);
console.log(cache.get(2)); // 预期:-1
cache.put(1, 10);
console.log(cache.get(1)); // 预期:10
console.log(cache.get(3)); // 预期:3

解题思路与实现

展开解题思路

利用 Map 保持键的插入顺序:最前面是最久未使用的键。读取命中或更新时先删除再插入,使该键移到末尾;满容量插入新键时淘汰迭代器返回的第一个键。

class LRUCache {
  constructor(capacity) {
    this.max = capacity;
    this.map = new Map();
  }
  get(key) {
    if (this.map.has(key)) {
      const value = this.map.get(key);
      // 删除后重新插入,使本次读到的键成为最近使用的一项。
      this.map.delete(key);
      this.map.set(key, value);
      return value;
    }

    return -1;
  }
  put(key, value) {
    if (this.map.has(key)) {
      this.map.delete(key);
      this.map.set(key, value);
    } else {
      if (this.map.size < this.max) {
        this.map.set(key, value);
      } else {
        // keys() 从最旧键开始迭代;next().value 才是键本身。
        const oldestKey = this.map.keys().next().value;
        this.map.delete(oldestKey);
        this.map.set(key, value);
      }
    }
  }
}

// 三组以上可直接运行的验证。
const cache = new LRUCache(2);
cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1)); // 预期:1
cache.put(3, 3);
console.log(cache.get(2)); // 预期:-1
cache.put(1, 10);
console.log(cache.get(1)); // 预期:10
console.log(cache.get(3)); // 预期:3

时间复杂度:平均 O(1)/次。Map 查找、删除、插入及取得迭代器首项按常见实现均为平均常数时间,不展开复制所有键。

空间复杂度:O(capacity)。Map 最多保存 capacity 个键值对,只使用常数额外临时空间。

下载原始 JS 文件