[编码题]LRU cache

66 阅读1分钟

LRU:least recently used. 是一种逐出策略,既可用在缓存中,也可用在操作系统的页面置换中,还有其他的类似场景。
LRU的算法,此处就不赘述了,很好理解。因为要把最近访问的元素放在队尾(或队首),所以用数组的操作复杂度是O(n),用链表的复杂度是O(1)。

最简单的cache一般要考虑这几个点:

  1. capacity,容量。
  2. data,存储缓存数据。一般是map结构,通过key快速定位到数据。
  3. 逐出策略。缓存是有容量的,所以需要一个逐出算法,替换data中的数据。
  4. key生成函数。
  5. 数据回源函数。
  6. Get函数。
  7. 回源策略。(本题目不涉及)

Leetcode.146

核心是链表的操作;这里有一个技巧,就是把 header 和 tail 设置成常驻的空节点,这样可以保证每个数据节点的 pre 和 next 不为空,少了很多 nil 判断。

type LRUCache struct {
   capacity int
   data     map[int]*Node
   header   *Node // 空节点
   tail     *Node // 空节点
}

type Node struct {
   key  int // 用于在 delete 的时候直接拿到 key
   val  int
   pre  *Node
   next *Node
}

func Constructor(capacity int) LRUCache {
   lc := LRUCache{
      capacity: capacity,
      data:     make(map[int]*Node, capacity),
      header:   &Node{},
      tail:     &Node{},
   }

   lc.header.next = lc.tail
   lc.tail.pre = lc.header

   return lc
}

func (this *LRUCache) Get(key int) int {
   res := -1 // default value
   if v, ok := this.data[key]; ok {
      res = v.val

      // 把 v 断开
      v.pre.next = v.next
      v.next.pre = v.pre

      // 把 v 加到 tail 前面
      v.pre = this.tail.pre
      v.next = this.tail
      v.pre.next = v
      v.next.pre = v
   }

   return res
}

func (this *LRUCache) Put(key int, value int) {
   if v, ok := this.data[key]; ok { // 已存在,只更新
      v.val = value
      this.Get(key) // 用get刷新
      return
   }

   for len(this.data) >= this.capacity { // 容量已满,先清理
      rmk := this.header.next.key
      delete(this.data, rmk)
      this.header.next = this.header.next.next
      this.header.next.pre = this.header
   }

   nn := &Node{key: key, val: value}
   this.data[key] = nn

   nn.pre = this.tail.pre
   nn.pre.next = nn
   nn.next = this.tail
   this.tail.pre = nn
}