LRU 缓存
难度:⭐⭐⭐ 面试超高频
考点
- 哈希表 + 双向链表
- 数据结构设计能力
- 字节面试必考题(出现率极高)
题目描述
设计并实现一个满足 LRU(最近最少使用)缓存约束的数据结构。
实现 LRUCache 结构体:
NewLRUCache(capacity int) *LRUCache— 以正整数作为容量初始化 LRU 缓存Get(key int) int— 如果 key 存在,返回 value;否则返回 -1Put(key int, value int)— 如果 key 已存在,更新 value;如果不存在,插入。当缓存容量达到上限时,在插入前淘汰最久未使用的 key
Get 和 Put 必须以 O(1) 平均时间复杂度运行。
函数签名
go
type LRUCache struct {
// 你的字段
}
func NewLRUCache(capacity int) *LRUCache
func (c *LRUCache) Get(key int) int
func (c *LRUCache) Put(key, value int)示例
cache := NewLRUCache(2)
cache.Put(1, 1) // 缓存:{1=1}
cache.Put(2, 2) // 缓存:{1=1, 2=2}
cache.Get(1) // 返回 1,缓存:{2=2, 1=1}(1 变成最近使用)
cache.Put(3, 3) // 容量已满,淘汰 key=2,缓存:{1=1, 3=3}
cache.Get(2) // 返回 -1(不存在)
cache.Put(4, 4) // 容量已满,淘汰 key=1,缓存:{3=3, 4=4}
cache.Get(1) // 返回 -1
cache.Get(3) // 返回 3
cache.Get(4) // 返回 4要求
- Get 和 Put 均为 O(1) 时间复杂度
- 需要自己定义双向链表节点(不使用 container/list)
提示
- 哈希表 map[int]*Node 提供 O(1) 查找
- 双向链表维护访问顺序:头部是最近使用,尾部是最久未使用
- 使用 dummy head 和 dummy tail 简化边界处理
- Get 时将节点移到头部
- Put 时:已存在则更新+移到头部;不存在则新建+加到头部,超容量则删尾部
参考答案(Go)
点击展开参考答案
go
//go:build ignore
package answer
type node struct {
key, val int
prev, next *node
}
type LRUCache struct {
capacity int
size int
cache map[int]*node
head, tail *node
}
func NewLRUCache(capacity int) *LRUCache {
head := &node{}
tail := &node{}
head.next = tail
tail.prev = head
return &LRUCache{
capacity: capacity,
cache: make(map[int]*node),
head: head,
tail: tail,
}
}
func (c *LRUCache) Get(key int) int {
if n, ok := c.cache[key]; ok {
c.moveToHead(n)
return n.val
}
return -1
}
func (c *LRUCache) Put(key, value int) {
if n, ok := c.cache[key]; ok {
n.val = value
c.moveToHead(n)
return
}
n := &node{key: key, val: value}
c.cache[key] = n
c.addToHead(n)
c.size++
if c.size > c.capacity {
removed := c.removeTail()
delete(c.cache, removed.key)
c.size--
}
}
func (c *LRUCache) addToHead(n *node) {
n.prev = c.head
n.next = c.head.next
c.head.next.prev = n
c.head.next = n
}
func (c *LRUCache) removeNode(n *node) {
n.prev.next = n.next
n.next.prev = n.prev
}
func (c *LRUCache) moveToHead(n *node) {
c.removeNode(n)
c.addToHead(n)
}
func (c *LRUCache) removeTail() *node {
n := c.tail.prev
c.removeNode(n)
return n
}