设计LRU缓存结构_牛客题霸_牛客网 设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 capacity ,操作次数是 n ,并有如下功能:
Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
get(key):如果关键字 key 存在于缓存中,则返回key对应的value值,否则返回 -1 。
set(key, value):将记录(key, value)插入该结构,如果关键字 key 已经存在,则变更其数据值 value,如果不存在,则向缓存中插入该组 key-value ,如果key-value的数量超过capacity,弹出最久未使用的key-value
提示: 1.某个key的set或get操作一旦发生,则认为这个key的记录成了最常使用的,然后都会刷新缓存。 2.当缓存的大小超过capacity时,移除最不经常使用的记录。 3.返回的value都以字符串形式表达,如果是set,则会输出”null”来表示(不需要用户返回,系统会自动输出),方便观察 4.函数set和get必须以O(1)的方式运行
想法: 用一个容器维护按最近访问时间的值,需要满足:
O(1)时间插入到最后,O(1)时间删除第一个,类似队列
O(1)时间找到元素,并且将元素移到队尾,实现更新
每次淘汰时,判断长度超容,然后淘汰第一个位置
为什么不用双端队列?双端队列只能操作首尾,且双端队列可能就是基于双链表实现的;而LRU需要移动中间的,所以使用双向链表
综上所述,该容器是有序、分散分布的,因此是一个双向链表,哈希表指向队列中节点的地址. ❌错误代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 class BiListNode: def __init__(self, val) -> None: self.val = val self.pre = None self.next = None class Solution: def __init__(self, capacity: int): self.length = 0 self.capacity = capacity self.head = None self.rear = None self.hashtable = {} def print_(self): this_node = self.head str_ = [] while this_node is not None: str_.append(this_node.val) this_node = this_node.next print(str_) def get(self, key: int) -> int: value = self.hashtable.get(key) if value is None: self.print_() return -1 else: if value.pre is not None: # 如果不是头节点 value.pre.next = value.next if value.next is not None: # 非尾节点 value.next.pre = value.pre # 插头 value.pre = None value.next = self.head self.head.pre = value self.head = value self.print_() return value.val def set(self, key: int, value: int) -> None: if self.length == 0: new_node = BiListNode(value) self.head = new_node self.rear = new_node self.length += 1 self.hashtable[key] = new_node self.print_() return node = self.hashtable.get(key) if node is not None: node.val = value # 插头 node.pre = None node.next = self.head self.head.pre = node self.head = node else: new_node = BiListNode(value) self.hashtable[key] = new_node # 插头 new_node.pre = None new_node.next = self.head self.head.pre = new_node self.head = new_node self.length += 1 if self.length > self.capacity: self.rear.pre.next = None rear = self.rear.pre del self.rear self.rear = rear self.print_()
根据代码调试需求,我发现当前实现存在以下几个关键问题:
🚨 核心问题分析
Node 结构缺失 key 字段 (关键缺陷)1 2 3 4 5 class BiListNode : def __init__ (self, val ) -> None : self.val = val self.pre = None self.next = None
后果 :当删除尾节点时无法获取对应的 key,导致哈希表无法正确清理旧数据
容量管理逻辑错误 1 2 3 4 5 if self.length > self.capacity: self.rear.pre.next = None rear = self.rear.pre del self.rear self.rear = rear
问题 :length 没有被正确减少,且删除尾节点时没有更新哈希表
链表操作边界条件问题
简单修正:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 class BiListNode: def __init__(self, key, val) -> None: self.key = key self.val = val self.pre = None self.next = None class Solution: def __init__(self, capacity: int): self.length = 0 self.capacity = capacity self.head = None self.rear = None self.hashtable = {} def print_(self): cur = self.head res = [] while cur: res.append(f"({cur.key}:{cur.val})") cur = cur.next print(" -> ".join(res)) def _move_to_head(self, node): """从链表当前位置,移动到头部""" if node == self.head: return # 已经是头部 # detach if node.pre: node.pre.next = node.next if node.next: node.next.pre = node.pre if node == self.rear: self.rear = node.pre # insert at head node.pre = None node.next = self.head if self.head: self.head.pre = node self.head = node if self.rear is None: # only one node self.rear = node def get(self, key: int) -> int: node = self.hashtable.get(key) if node is None: return -1 self._move_to_head(node) return node.val def set(self, key: int, value: int) -> None: node = self.hashtable.get(key) if node: # 如果 key 存在 node.val = value self._move_to_head(node) else: new_node = BiListNode(key, value) self.hashtable[key] = new_node # 插入到头部 new_node.pre = None new_node.next = self.head if self.head: self.head.pre = new_node self.head = new_node if not self.rear: self.rear = new_node self.length += 1 if self.length > self.capacity: # 删除尾部节点 to_remove = self.rear if to_remove.pre: to_remove.pre.next = None self.rear = to_remove.pre del self.hashtable[to_remove.key] del to_remove self.length -= 1 def debug(self): self.print_()
✅ 正确实现方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class BiListNode : def __init__ (self, key, val ): self.key = key self.val = val self.pre = None self.next = None class Solution : def __init__ (self, capacity: int ): self.capacity = capacity self.hashtable = {} self.dummy_head = BiListNode(-1 , -1 ) self.dummy_tail = BiListNode(-1 , -1 ) self.dummy_head.next = self.dummy_tail self.dummy_tail.pre = self.dummy_head def _remove_node (self, node ): """移除指定节点""" prev = node.pre next = node.next prev.next = next next .pre = prev def _add_to_head (self, node ): """添加节点到头部""" node.next = self.dummy_head.next node.pre = self.dummy_head self.dummy_head.next .pre = node self.dummy_head.next = node def get (self, key: int ) -> int : if key not in self.hashtable: return -1 node = self.hashtable[key] self._remove_node(node) self._add_to_head(node) return node.val def set (self, key: int , value: int ) -> None : if key in self.hashtable: node = self.hashtable[key] node.val = value self._remove_node(node) self._add_to_head(node) else : if len (self.hashtable) >= self.capacity: last_node = self.dummy_tail.pre self._remove_node(last_node) del self.hashtable[last_node.key] new_node = BiListNode(key, value) self.hashtable[key] = new_node self._add_to_head(new_node)
🛠️ 关键改进点说明
改进点
原代码问题
新方案优势
Node 结构
无法追踪 key 导致哈希表泄漏
通过存储 key 实现完整清理
Dummy 节点
头尾边界处理复杂
统一操作逻辑,避免空指针
容量管理
length 计数不准确
直接通过哈希表大小判断
链表操作
分散的指针操作易出错
封装成 _remove_node 和 _add_to_head
🔍 测试用例验证 1 2 3 4 5 6 cache = Solution(2 ) cache.set (1 , 100 ) cache.set (2 , 200 ) print (cache.get(1 )) cache.set (3 , 300 ) print (cache.get(2 ))
建议结合双向链表的基础原理(参考 labuladong.online )和 LRU 的标准实现模式进行理解。