LeetCode 146. LRU 缓存

56 阅读2分钟

核心需求

设计一个 LRU(最近最少使用)缓存,支持 get 和 put 操作,要求时间复杂度 O(1)

设计思路

  1. 哈希表 + 双向链表

    • 哈希表 (unordered_map<int, Node*>) :提供 O(1) 的键值查找。

    • 双向链表:维护访问顺序,头部是最久未使用的节点,尾部是最近使用的节点。

      • 链表节点包含 keyvalue 及前驱/后继指针。
  2. 哨兵节点

    • 使用虚拟头节点 (head) 和尾节点 (tail),简化链表操作(避免空指针判断)。
  3. 关键操作

    • get(key)

      1. 若 key 不存在,返回 -1。
      2. 若存在,通过哈希表定位节点,将其移到链表尾部(标记为最近使用),返回值。
    • put(key, value)

      1. 若 key 存在:更新值,并移到尾部。

      2. 若 key 不存在:

        • 容量已满:删除链表头部节点(最久未使用),并从哈希表删除。
        • 创建新节点:添加到链表尾部,并存入哈希表。

为什么这么设计?

  • O(1) 查找:哈希表直接定位节点。
  • O(1) 顺序调整:双向链表支持快速删除任意节点(通过前驱/后继指针)和尾部插入。
  • 容量管理:链表头部始终是最久未使用的节点,删除操作 O(1)。
  • 哨兵节点:简化链表边界操作(如删除头节点时无需特殊处理)。
type LRUCache struct {
    Capacity int 
    Cache map[int]*Node
    Head *Node
    Tail *Node 
}

type Node struct {
    Prev *Node
    Next *Node 
    Key int 
    Value int 
}

func Constructor(capacity int) LRUCache {
   m := make(map[int]*Node, capacity)
   head := &Node{}
   tail := &Node{}
   head.Next = tail
   tail.Prev = head
   return LRUCache{
       Capacity: capacity,
       Cache : m, 
       Head : head,
       Tail : tail,
   }
}


func (this *LRUCache) Get(key int) int {
    node, ok := this.Cache[key]
    if !ok {
        return -1
    }

    // 如果存在: 1. 将其在链表中删除; 2. 增加到 list 头部
    // 此时 map 不涉及更新操作
    this.move2Head(node)
    return node.Value
}

func (this *LRUCache) move2Head(node *Node) {
    this.deleteNode(node)
    this.add2Head(node)
}


// 链表中删除某个节点只需要操作其前后 node 节点的 prev、next 即可。
func (this *LRUCache) deleteNode(node *Node) {
    node.Prev.Next = node.Next
    node.Next.Prev = node.Prev
}

func (this *LRUCache) add2Head(node *Node) {
    tmp := this.Head.Next
    this.Head.Next = node 
    node.Prev = this.Head
    node.Next = tmp
    tmp.Prev = node
}

func (this *LRUCache) Put(key int, value int)  {
    // 这里不适合根据 this.Cache的长度进行 if else 判断  因为这两个都需要后面再根据 cache 是否存在这个 key 在进行逻辑处理
    // 所以这里直接根据 cache 中是否包含这个 key 来进行处理
    node, ok := this.Cache[key]    
    if ok {
       // 如果存在,则1. cache更新 node value  2. 移动 node 到head
       node.Value = value
       this.move2Head(node) 
       return
    }

    // 判断长度是否需要逐出
    if len(this.Cache) == this.Capacity {
        // 先逐出  1. cache 删除 tailNode 2. list 删除 tailNode
        tailNode := this.Tail.Prev
        delete(this.Cache, tailNode.Key)
        this.deleteNode(tailNode)
    }

  
    // 2. 执行添加到 cache  添加到 list
    newNode := &Node {
        Key : key,
        Value : value,
    }

    this.Cache[key] = newNode
    this.add2Head(newNode)
}