【算法】—— LRU

193 阅读2分钟

LRU算法

题目描述:

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。

它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

题目地址:面试题 16.25. LRU 缓存

解法1:使用栈

由于每次get的时候要遍历栈,所以时间复杂度最坏要整个遍历一遍为O(n)

class LRUCache {
  stack: number[] = []
  map: Record<number, number> = {}
  maxLength: number
  constructor(capacity: number) {

    this.maxLength = capacity

  }

  get(key: number): number {
    if (this.map[key] == undefined) {
      return -1
    } else {
      const index = this.stack.findIndex(val => val === key)
      const [val] = this.stack.splice(index, 1)
      this.stack.unshift(key)
      return this.map[key]
    }
  }

  put(key: number, value: number): void {

    if (this.map[key] !== undefined) {
      const index = this.stack.findIndex(val => val == key)
      this.stack.splice(index, 1)
      this.stack.unshift(key)
    } else {
      this.stack.unshift(key)
      if (this.stack.length > this.maxLength) {
        const last = this.stack.pop()
        delete this.map[last]
      }
    }
    this.map[key] = value
  }
}

解法2:使用map配合双向链表

  • map作用:可以以时间复杂度O(1)获取到元素,map的值为链表的节点
  • 双向链表:可以记录节点的前驱和后继,方便插入和删除
  • 创建头部和尾部虚拟节点,方便插入和删除处理边界问题

具体代码如下:


class MyLinkNode {

  constructor(val: number = null, key: number = null, prev: MyLinkNode = null, next: MyLinkNode = null) {
    this.val = val
    this.key = key
    this.prev = prev
    this.next = next
  }
  val: number
  key: number
  prev: MyLinkNode
  next: MyLinkNode

}

class LRUCache {

  maxLength: number
  tail: MyLinkNode//虚拟尾结点
  head: MyLinkNode//虚拟头结点
  map = {}
  count: number = 0
  constructor(capacity: number) {
    this.maxLength = capacity
    const head = new MyLinkNode()
    const tail = new MyLinkNode()

    head.next = tail
    tail.prev = head
    this.head = head
    this.tail = tail

  }

  get(key: number): number {

    const find = this.map[key]
    if (find) {
      // 删除节点在链表的位置,并插入到头部
      const node = this.removeNode(find)
      this.insertHead(node)
      return node.val
    } else {
      return -1
    }

  }

  removeNode(node: MyLinkNode): MyLinkNode {
  //从链表中删除该节点
    const prev = node.prev
    const next = node.next
  
    prev.next = next
    next.prev = prev
    // node.prev = null
    // node.next = null
    return node
  }

  insertHead(node) {
  //插入到头结点
    const next = this.head.next
    node.prev = this.head
    node.next = next
    next.prev = node
    this.head.next = node
    // console.log('insertHead',this.head)
  }

  put(key: number, value: number): void {
    if (!this.map[key]) {
      const node = new MyLinkNode(value, key)

      this.count++
      if (this.count > this.maxLength) {
        //删除尾节点
        const node = this.removeNode(this.tail.prev)
        delete this.map[node.key]
      }
      this.insertHead(node)
      this.map[key] = node
    } else {
      const node = this.map[key]
      node.val = value
      //由于之前存在该节点,则直接获取更改值即可,为了调整顺序到最前,所以再调用一下get
      this.get(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)
 */

image.png