leetcode hot100 146. LRU 缓存 medium OrderedDict 双向链表 双向字典 哈希表

常见缓存淘汰策略

  1. FIFO(First In First Out)先进先出策略会将数据按照写入缓存的顺序进行排队,当缓存空间不足时,最先进入缓存的数据会被优先删除。是一种比较死板的策略不考虑数据热度可能会淘汰大量的热点数据,但是实现起来相对容易。

  2. LRU(Least Recently Used)是最经典的内存淘汰策略,其设计原则是"如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小"。即根据数据的最近访问时间来进行淘汰,缺点是可能会由于一次冷数据的批量查询而误删除大量的热点数据

  3. TTL(Time ToLive)是指用户为缓存设置的过期时间,当前时间到达过期时间时将删除缓存;如果缓存空间已满,则优先淘汰最接近过期时间的数据。

  4. LFU(Least Frequently Used)策略会记录每个缓存数据的最近访问次数(频率),并优先清除使用次数较少的数据。这种算法存在的显著缺点是,最新写入的数据由于访问次数少,常常刚被缓存就删除

  5. Random 随机淘汰策略,一般不建议使用。


  • 题目要求 get 和 put 都要在 O ( 1 ) O(1) O(1) 时间内完成
  • 要将两种完全不同的数据结构结合起来:哈希表(Hash Table) 和 双向链表(Doubly Linked List)。
  1. 哈希表:查询快。给定一个 key,能瞬间找到它对应的 value。但它没有顺序,无法知道谁是"最近使用"的。
  2. 链表:有顺序。我们可以约定:靠近头部的节点是"最近使用的",靠近尾部的是"最久未使用的"。移动节点非常快。
  3. 双向链表:要把某个中间节点删除并移动到头部,只有双向链表能在 O ( 1 ) O(1) O(1) 内拿到它的前驱节点。

LRU规则

  • 访问即更新:无论是 get(key) 还是 put(key, val),只要这个 key 被碰过了,它就变成了"最受宠"的,必须移动到链表的最前面(头)。
  • 满员则淘汰:当缓存满了还要 put 新东西时,直接把链表**最后面(尾)**的那个节点踢掉,同时在哈希表里也删掉它。

示例动态拆解

比如一个果篮大小为3 apple orange banana依次进入 {banana,orange,apple}

你想拿去苹果看一眼再放回果篮 {apple,banana,orange}

现在来了新的水果peach超出果篮大小需要丢掉一个水果 {peach,apple,bnana}

可以得出规律 新来的放最前面 刚访问过的移动到最前面 空间满了就把最旧的淘汰掉

python 复制代码
LRUCache lRUCache = new LRUCache(2);  // 假设容量为 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

python collections.OrderedDict 内部其实维护了一个哈希表 + 双向链表

它不仅简洁,而且完全符合 O ( 1 ) O(1) O(1) 的时间复杂度要求。

在 OrderedDict 的 LRU 实现中,最常见的做法是:

  • 末尾 (End/Right):定义为 最新使用 的位置。
  • 开头 (Head/Left):定义为 最久未使用 的位置。

OrderedDict popitem() 方法

  • last=True (默认值):按照 "后进先出" (LIFO) 原则。弹出最后面(最右边)插入的元素。这就像从一叠盘子的最上面拿走一个。
  • last=False:按照 "先进先出" (FIFO) 原则。弹出最前面(最左边)插入的元素。这正是 LRU 需要的------踢掉那个最早进来且一直没被更新过的人。

时间复杂度:get 和 put 都是 O ( 1 ) O(1) O(1)。哈希表查找 O ( 1 ) O(1) O(1),双向链表断开和连接指针也是 O ( 1 ) O(1) O(1)。

空间复杂度: O ( n ) O(n) O(n)。哈希表和双向链表最多各存储 n 个元素。

python 复制代码
# class Node:
#     def __init__(self):
#         # 实现键值和前驱与后继
#         self.key = key
#         self.value = value
#         self.prev = None
#         self.next = None

from collections import OrderedDict        

class LRUCache:

    def __init__(self, capacity: int):  # 变量名capacity
        self.capacity = capacity    # 传入参数capacity 初始化, capacity字典容量
        self.cache = OrderedDict()  # 库函数OrderedDict 初始化, 新建字典
        
    # 取值
    def get(self, key: int) -> int:
        #  没有返回-1
        if key not in self.cache:
            return -1
        
        # 如果有移动到最新并返回值
        self.cache.move_to_end(key)  # 在 OrderedDict 的 LRU 实现中:末尾 (End/Right) 定义为 最新使用 的位置。
        return self.cache[key]

        
    # 存值
    def put(self, key: int, value: int) -> None:
        # # 如果值已经存在 移动该值到最新
        if key in self.cache:
            self.cache.move_to_end(key)  
        self.cache(key) = value  # key的值可能会更新,重新赋值

        # 弹出最旧 最先放进去的那个
        if len(self.cache) > self.capacity:  # 大于字典容量
            self.cache.popitem(last=False)   # last=False 弹出最左边的(最早的)
        






# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
相关推荐
季明洵6 小时前
Java实现顺序表
java·数据结构·算法·顺序表
im_AMBER6 小时前
Leetcode 110 奇偶链表
数据结构·学习·算法·leetcode
智码未来学堂15 小时前
探秘 C 语言算法之枚举:解锁解题新思路
c语言·数据结构·算法
青桔柠薯片17 小时前
数据结构:顺序表与链表
数据结构·链表
金枪不摆鳍18 小时前
算法--二叉搜索树
数据结构·c++·算法
向哆哆18 小时前
画栈 · 跨端画师接稿平台:基于 Flutter × OpenHarmony 的整体设计与数据结构解析
数据结构·flutter·开源·鸿蒙·openharmony·开源鸿蒙
季明洵19 小时前
C语言实现顺序表
数据结构·算法·c·顺序表
历程里程碑1 天前
Linxu14 进程一
linux·c语言·开发语言·数据结构·c++·笔记·算法
Snow_day.1 天前
有关线段树应用(1)
数据结构·算法·贪心算法·动态规划·图论