leetcode146. LRU 缓存【python3哈希表+双向链表】利用OrderedDict以及自实现双向链表

news2025/1/12 16:08:48

题目:

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

  • LRUCache(int capacity) 以正整数作为容量capacity初始化 LRU 缓存
  • int get(int key) 如果关键字key存在于缓存中,则返回关键字的值,否则返回-1 。
  • void put(int key, int value) 如果关键字key已经存在,则变更其数据值value;如果不存在,则向缓存中插入该组key-value。如果插入操作导致关键字数量超过capacity,则应该 逐出 最久未使用的关键字。
  • 函数get和put必须以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); // 返回 1lRUCache.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); // 返回 3lRUCache.get(4); // 返回 4

标题题解

该题目的核心主要是get与put函数。get函数的要求非常简单就是通过key获取相应的value,hash表即可满足题目要求。put函数主要超过capacity的时候需要把最久未使用的关键字删除,再添加新的key-value。这里显然需要对key进行排序,自然想到的就是Python3中collections中的OrderedDict。代码如下:

class LRUCache:

    def __init__(self, capacity: int):
        #定义一个dict
        self.capacity=capacity
        #定义一个有序字典
        self.LRU_dic=collections.OrderedDict()
        #使用一个变量记录dic的数据量
        self.used_num=0

    def get(self, key: int) -> int:
        if key in self.LRU_dic:
            #如果被使用了利用move_to_end移动到队尾,保证了LRU_dic是按使用顺序排序的
            self.LRU_dic.move_to_end(key)
            return self.LRU_dic.get(key)
        else:
            return -1

    def put(self, key: int, value: int) -> None:
        if key in self.LRU_dic:         
            self.LRU_dic[key]=value
            self.LRU_dic.move_to_end(key)
        else:
            #判断当前已有数量是否超过capacity
            if self.used_num==self.capacity:
                #删除首个key,因为首个key是最久未使用的
                self.LRU_dic.popitem(last=False)
                self.used_num-=1
            self.LRU_dic[key]=value
            self.used_num+=1

然后,直接调用包通常不是面试的时候考察的主要能力。因此需要根据一些数据结构来实现OrderedDict类似的功能。
可以发现哈希表肯定是需要的,能存储key,value相关信息。这里一个主要的需求是一个序的数据结构能在头部与尾部都实现O(1)时间复杂度的操作。list也能满足这个要求,但是list对于元素的查询、更新以及有排序操作的支持不是很友好。显然这里使用双向链表比较合适。下面详细介绍一下哈希表与双向链表。数据结构清楚了实现就比较容易了。
先介绍下双向链表。
image.png
pre表示前向指针,key,value就是键值,next表示下一步指针指的节点。初始化只有head与tail两个节点,这里把新使用的节点放到链表尾部,头部链表节点就是最久未使用的节点。
image.png
因为使用过的key需要把相应的链表节点移动到队尾,因此需要实现类似OrderedDict的move_to_end这个函数的功能。如果超出capacity的时候要删除最久未使用的节点也就是删除head.next节点,也需要实现一个表头节点删除的功能。定义完链表,hash表就好实现了key,对应给定的key,value对应的是双向链表中的节点。这样通过key就能在O(1)时间定位到双向链表中的节点。代码实现如下:

class LRUCache:
    class D_link:
        def __init__(self,key=0,value=0):
            self.key=key
            self.value=value
            self.pre=None
            self.next=None


    def __init__(self, capacity: int):
        #定义一个dict
        self.capacity=capacity
        self.LRU_dic={}
        #使用一个变量记录dic的数据量
        self.used_num=0
        #初始化双向链表
        self.head=LRUCache.D_link()
        self.tail=LRUCache.D_link()
        self.head.next=self.tail
        self.tail.pre=self.head

    def get(self, key: int) -> int:
        if key in self.LRU_dic:
            node=self.LRU_dic.get(key)
            #因为访问过需要移动到链表尾
            self.move_to_end(node)
            return node.value
        else:
            return -1

    def put(self, key: int, value: int) -> None:
        if key in self.LRU_dic:
            #更新LRU_dic的value         
            node= self.LRU_dic[key]
            node.value=value
            self.move_to_end(node)
        else:
            #判断当前已有数量是否超过capacity
            if self.used_num==self.capacity:
                #删除双链表最前面的Node
                del_key=self.del_head()
                self.used_num-=1
                #从LRU_dic中删除Key
                del self.LRU_dic[del_key]
            new_node=LRUCache.D_link(key,value)
            #在链表尾部插入
            self.insert_node(new_node)
            self.LRU_dic[key]=new_node
            self.used_num+=1
    
    def move_to_end(self,node):
        #首先把node取出来
        pre_node=node.pre
        post_node=node.next
        pre_node.next=post_node
        post_node.pre=pre_node
        tail_pre=self.tail.pre
        tail_pre.next=node
        node.pre=tail_pre
        node.next=self.tail
        self.tail.pre=node

    def del_head(self):
        key=self.head.next.key
        post_node=self.head.next.next
        self.head.next=post_node
        post_node.pre=self.head
        return key

    def insert_node(self,node):
        pre_tail=self.tail.pre
        pre_tail.next=node
        node.pre=pre_tail
        node.next=self.tail
        self.tail.pre=node

计算复杂度

  • 时间复杂度,题目要求就是 O ( 1 ) O(1) O(1),这里也是 O ( 1 ) O(1) O(1)
  • 空间复杂度,因为链表长度要求是在capacity下的,因此空间复杂度为 O ( c a p a c i t y ) O(capacity) O(capacity)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/156641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文速递】9位院士Science88页长文:人工智能的进展、挑战与未来

【论文速递】9位院士Science88页长文:人工智能的进展、挑战与未来 【论文原文】:Intelligent Computing: The Latest Advances, Challenges and Future 获取地址:https://spj.science.org/doi/10.34133/icomputing.0006摘要: ​…

【阶段三】Python机器学习15篇:机器学习项目实战:支持向量机回归模型

本篇的思维导图: 项目实战(支持向量机回归模型) 项目背景 股票投资(Stock Investment)是指企业或个人用积累起来的货币购买股票,借以获得收益的行为。股票投资的收益是由“收入收益”和“资本利得”两部分构成的。收入收益是指股票投资者以股东身份,按照持股的份…

大网规划部署刷题讲解(带答案)

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 一.大网规划刷题 前言 本章将会讲解大网规划刷题的讲解。 一.大网规划刷题 …

ArcGIS基础实验操作100例--实验85创建线要素间的最近垂线

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台:ArcGIS 10.6 实验数据:请访问实验1(传送门) 高级编辑篇--实验85 创建线要素间的最近垂线 目录 一、实验背景 二、实验数据 三、实验步骤 &#xf…

【一文速通】数据分布不同解决办法

1. 构造合适的验证集当出现训练集和测试集分布不一致的,我们可以试图去构建跟测试集分布近似相同的验证集,保证线下验证跟线上测试分数不会抖动,这样我们就能得到稳定的benchmark。Qiuyan918在基于对抗验证的基础上,提出了三种构造…

为什么流媒体直播的延迟很高

通信技术的发展促进了视频点播和直播业务的兴起,4G 和 5G 网络技术的进步也使得流媒体技术变得越来越重要,但是网络技术并不能解决流媒体直播的高延迟问题,本文不会介绍网络对直播业务的影响,而是会分析直播中常见的现象 — 主播和…

Spring(1):拦截器

1 拦截器 拦截器是Spring中的概念,和过滤器类似,可以对用户请求进行拦截过滤处理。但是相对于过滤器而言,拦截器要的控制更加的细节,拦截器可以在三个地方进行执行: 可以在请求达到Controller控制器之前,…

VirtualBox 客户机/虚拟机无法时间同步?可能是你安装客户机插件的方式不对!

文章目录安装客户机插件的正确方式准备工作安装过程用户手册中的“客户机插件”安装客户机插件的正确方式 准备工作 所需包如下: GNU compiler (GCC)GNU Make (make)Kernel header fileslib* files 笔者亲测使用的 CentOS 7 Linux 内核版本为 3.10.0-1160.81.1.…

SQL索引概念(详解B+树)

SQL索引定义分类复合索引特性复合索引最左特性(原则)原理索引及其扫描类型索引的优缺点优点:缺点:索引工作原理BTree索引怎么判断是否创建索引?为什么Mysql用B树做索引而不用B-树或红黑树为什么索引快?定义 索引是一种排好序的快…

【数据结构】认清带头双向循环链表的庐山真面目

目录前言一、带头双向循环链表的介绍二、带头双向循环链表的类型重定义1.对数据类型进行重定义2.链表结点结构3.结点类型重定义三、常见函数操作的实现1.声明2.定义1. 申请新节点2. 初始化3. 销毁链表4. 打印链表5. 尾插数据6. 尾删数据7. 头插结点8.头删结点9. 在指定的位置前…

嵌入式开发的程序架构

前言 在嵌入式软件开发,包括单片机开发中,软件架构对于开发人员是一个必须认真考虑的问题。 软件架构对于系统整体的稳定性和可靠性是非常重要的,一个合适的软件架构不仅结构清晰,并且便于开发。 我相信在嵌入式或单片机软件开发…

聚焦云原生安全|安全狗亮相云原生产业联盟年会

1月9日,云原生产业联盟年会成功举办。 作为国内云原生安全领导厂商,安全狗也受邀参与此次大会。 安全狗高级副总裁陈荣有发表寄语 在此次线上会议中,安全狗凭借突出的云原生安全整体实力,通过层层筛选与审核,入选成为…

OpenCV从3D-2D 点对应中查找对象姿势solvePnP

1.概述:在使用相机拍照片时,大多数人会考虑拍的好不好看,关注相机中物体坐标的并不多,但是对于地信学科来说,如果能从照片中获取物体的真实位置,对地理信息获取大有帮助,在这里面,十…

深入分析Linux PCI驱动框架(三)

说明: Kernel版本:4.14ARM64处理器使用工具:Source Insight 3.5, Visio 1. 概述 先回顾一下PCIe的架构图: 本文将讲PCIe Host的驱动,对应为Root Complex部分,相当于PCI的Host Bridge部分&…

Vue 总结四 (ref, mixin, 插件, 插槽, VueX)

目录 ref 混入 mixin 插件 插槽 使用插槽的情景 使用方法 VueX 使用场景 使用 state 存放共享数据 actions 操作共享数据的API mutations 操作共享数据的API 生命周期图 ref 和id的区别 对于传统标签来说没有区别 都拿到的是 html内容 对于自定义的vue 的标签…

Spring事务源码分析

1. 前言 Spring支持两种事务管理的方式:声明式事务和编程式事务。编程式事务的优点是可以在代码里控制事务的粒度,实现细粒度的事务控制,缺点是对业务代码存在侵入性,代码复杂度较高,一般很少使用。声明式事务的优点是…

Linux下的动静态库

目录 认识动静态库 如何制作动静态库? 静态库 动态库 使用库 使用静态库 使用动态库 为什么动态链接是如此呢? 认识动静态库 我们在使用标准库的时候,需要有系统的头文件和系统的库文件,这个库文件是什么呢? …

Databend 借助对象存储帮你实现降本增效

本篇文章围绕着: 什么是对象存储当 Databend 遇到对象存储2022 年 Databend 利用对象存储降本的案例国内优秀的对象存储产品基于对象存储创业的产品 什么是对象存储 对象存储是一种可以非结构化存储和管理数据的技术。 可以简单理解为 NoSQL 接口方式存储和访问数…

linux系统中使用QT实现多媒体的功能方法

大家好,今天主要和大家聊一聊,如何使用QT中的多媒体的功能。 目录 第一:多媒体基本简介 第二:应用实例实现 第三:程序运行效果 第一:多媒体基本简介 QT的多媒体模块提供了音频,视频&#xff…

分布式系统-CAP 理论

在前一篇分布式系统–拜占庭将军问题(The Byzantine Generals Problem) 我们理解了共识问题的背景,这一节主要讨论如何解决或者理解自己系统中的共识问题,通过什么来分辨自己的系统需要哪一种共识。 这个理论就是 CAP 理论,先想下面几个问题…