核心需求
设计一个 LRU(最近最少使用)缓存,支持 get 和 put 操作,要求时间复杂度 O(1) 。
设计思路
-
哈希表 + 双向链表
-
哈希表 (
unordered_map<int, Node*>) :提供 O(1) 的键值查找。 -
双向链表:维护访问顺序,头部是最久未使用的节点,尾部是最近使用的节点。
- 链表节点包含
key、value及前驱/后继指针。
- 链表节点包含
-
-
哨兵节点
- 使用虚拟头节点 (
head) 和尾节点 (tail),简化链表操作(避免空指针判断)。
- 使用虚拟头节点 (
-
关键操作
-
get(key):- 若 key 不存在,返回 -1。
- 若存在,通过哈希表定位节点,将其移到链表尾部(标记为最近使用),返回值。
-
put(key, value):-
若 key 存在:更新值,并移到尾部。
-
若 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)
}