Q35-code146- LRU 缓存

47 阅读2分钟

实现思路

1 方法1: 循环链表 + Map

1.1 get:

  • 需要一个 MyNode,来存储 { key: val }
  • 需要一个 Map, 来O(1)判断 是否存在;存在 ? node.val : -1
  • 如果node存在, 需要把它移动到最前面:moveFirst
  • moveFirst 可以被拆解/转化为是:delNode + addFirst

1.2 put:

  • 需要一个Map, 来O(1)判断 是否存在:
    • 存在: 更新 node.val + moveFirst + 对应Map记录值 更新
    • 不存在: 新构造 MyNode实例后,addFirst + 创建 对应Map记录值
  • 如果此时 Map.size > Cap, 则要:
    • 删除最后一个接口:delLast,可以看做是特定位置的 delNode
    • 同时还要删除掉 Map里 对应的LastNode记录: 即通过node.key 反删 对应Map

1.3 由上可知,我们需要辅助的操作有3个:

  • moveFirst: 又可以被拆解/转化为: delNode + addFirst
  • addFirst: 单向链表,就能实现是O(1)的
  • delNode(包括了delLast): 想在O(1)内实现,就必须使用 双向/循环 列表了

1.4 易错点:

  • 循环链表:要始终保证dummy.pre 指向最后一个节点
  • 删除最后一个节点时,需要同时删除掉 Map里的记录

2 方法2 Map + Map.keys()的Iter有序性

2.1 利用了Map的有序性:

  • ES6 Map的特性:Map会记住键的插入顺序
  • Map.keys() 会返回一个 MapIterator 对象,它是一个迭代器,包含了 Map 中所有键的值, 它会按照插入顺序排列
  • 所以只要通过先del再Set的操作,就能始终保证最右边的是最新的,最左边的是最旧的

参考文档

01- 方法1参考实现

02- 方法2参考实现

代码实现

1 方法1: 循环链表 + Map 时间复杂度: O(1); 空间复杂度(n)

class LRUCache {
  record: Map<number, MyNode>;
  cap: number;
  link: dbLinked;

  constructor(capacity: number) {
    this.cap = capacity;
    this.record = new Map();
    this.link = new dbLinked();
  }

  get(key: number): number {
    const node = this.record.get(key);
    const res = node ? node.val : -1;
    if (node) this.link.moveFirst(node);
    return res;
  }

  put(key: number, value: number): void {
    const node = this.record.get(key);
    if (node) {
      node.val = value;
      this.link.moveFirst(node);
      this.record.set(key, node);
    } else {
      const curNode = new MyNode(key, value);
      this.link.addFirst(curNode);
      this.record.set(key, curNode);
    }
    if (this.record.size > this.cap) {
      // 易错点3: 删除最后一个节点时,需要同时删除掉 Map里的记录
      const lastNode = this.link.delLast();
      this.record.delete(lastNode.key);
    }
  }
}

class MyNode {
  key: number;
  val: number;
  pre: MyNode | null;
  next: MyNode | null;
  // 易错点1: 循环链表
  constructor(key = -1, val = -1) {
    this.key = key;
    this.val = val;
    this.pre = this;
    this.next = this;
  }
}

class dbLinked {
  dummy: MyNode;

  constructor() {
    this.dummy = new MyNode();
  }

  // moveFirst: delNode + addFirst
  moveFirst(node) {
    this.delNode(node);
    this.addFirst(node);
  }

  addFirst(node) {
    // 易错点2: 要始终保证dummy.pre 指向最后一个节点,而不是之前的头节点
    // this.dummy.pre = this.dummy.next || node;
    this.dummy.next.pre = node;
    node.pre = this.dummy;

    node.next = this.dummy.next;
    this.dummy.next = node;
  }

  delNode(node) {
    node.next.pre = node.pre;
    node.pre.next = node.next;
    node.pre = node.next = null;
  }

  delLast() {
    const lastNode = this.dummy.pre;
    // 空链表情况,此题默认保证不会出现这种情况
    if (lastNode === this.dummy) return null; 
    this.delNode(lastNode);
    return lastNode;
  }
}

2 方法2:Map + Map.keys()的Iter有序性 时间复杂度: O(1); 空间复杂度(n)

class LRUCache {
  cache: Map<number, number>;
  cap: number;

  constructor(capacity: number) {
    this.cache = new Map();
    this.cap = capacity;
  }

  get(key: number): number {
    const val = this.cache.get(key);
    if (val == null) return -1;
    // 如果存在key, 则先del,再set,从而更新key的位置 到最新的
    this.cache.delete(key);
    this.cache.set(key, val);
    return val;
  }

  put(key: number, value: number): void {
    this.cache.delete(key);
    this.cache.set(key, value);
    if (this.cache.size > this.cap) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
  }
}