146. LRU 缓存

https://leetcode.cn/problems/lru-cache

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。 函数 getput 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

提示:

1 <= capacity <= 3000 0 <= key <= 10000 0 <= value <= 105 最多调用 2 * 105 次 get 和 put

解题思路

实现功能

主要功能是 Get 和 Put,先分析一下这两个方法需要实现什么功能

Get

  • 获取缓存值
  • 提高key的优先级

Put

  • key 已存在
    • 设置缓存
    • 设置当前key 的优先级为最高
  • key 不存在
    • 设置缓存
    • 设置当前key 的优先级为最高
    • 检查缓存容量

数据结构

首先是缓存内容,便于存取,使用map 最合适

其次是优先级,他需要需要支持

  1. 将某个key的优先级提到最高
  2. 删除优先级最低的key

这里就考虑到了 切片、链表

  • 切片:删除key 比较方便。但是定位key来提升优先级需要 O(n) 的复杂度
  • 链表:删除key 需要知道表尾,用双向链表。提升优先级将对应的节点提到表头即可。双向链表完成这两个能力的成本更低。

代码


type Node struct {
    key, val  int
    pre, next *Node
}

type LRUCache struct {
    m          map[int]*Node // 缓存映射
    head, tail *Node         // 双向链表,维护优先级,head 指向优先级最高
    cap        int           // 最大容量
    size       int           // 当前缓存数量
}

func Constructor(capacity int) LRUCache {
    l := LRUCache{
        m:    make(map[int]*Node, capacity),
        head: &Node{},
        tail: &Node{},
        cap:  capacity,
    }
    l.head.next = l.tail
    l.tail.pre = l.head
    return l
}

/*
*
Get 获取缓存

1. 获取缓存值
2. 提高当前key 的优先级
*/
func (this *LRUCache) Get(key int) int {
    node, ok := this.m[key]
    if ok {
        this.moveToHead(node)
        return node.val
    }
    return -1
}

/*
*
Put 设置缓存

两种情况:
case1:key 已存在
1. 设置缓存
2. 提高当前key 的优先级

case2: key 不存在
1. 设置缓存
2. 在表头插入key
3. 检查缓存容量
*/
func (this *LRUCache) Put(key int, value int) {
    node, ok := this.m[key]
    if ok {
        node.val = value
        this.moveToHead(node)
    } else {
        node = &Node{
            key: key,
            val: value,
        }
        this.m[key] = node
        this.addToHead(node)
        this.size++
    }
    for this.size > this.cap {
        tail := this.removeTail()
        delete(this.m, tail.key)
        this.size--
    }
}

func (this *LRUCache) addToHead(node *Node) {
    node.pre = this.head
    node.next = this.head.next
    this.head.next.pre = node
    this.head.next = node
}

func (this *LRUCache) removeNode(node *Node) {
    node.pre.next = node.next
    node.next.pre = node.pre
}

func (this *LRUCache) moveToHead(node *Node) {
    this.removeNode(node)
    this.addToHead(node)
}

func (this *LRUCache) removeTail() *Node {
    node := this.tail.pre
    this.removeNode(node)
    return node
}