Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何在O(1)时间复杂度下实现LRU

如何在O(1)时间复杂度下实现LRU

作者头像
用户7685359
发布于 2020-08-22 10:03:18
发布于 2020-08-22 10:03:18
64100
代码可运行
举报
文章被收录于专栏:FluentStudyFluentStudy
运行总次数:0
代码可运行

一、题意分析

通常我们会把频繁用到的数据放到缓存里,这样取数据比较快,但内存有限,所以经常会有一些淘汰策略,LRU就是其中一种,他的原理是:我们认为最近访问(包括 get 和 set)操作的数据,最有可能是接下来即将用到的数据,当达到一定数量时,我们淘汰掉最近都没有访问的数据

这里需要注意的是,get 操作也算是“访问”了一次数据,显然 put 也算,因为最近插入的数据,极大可能是我马上要用到的数据

其实想要单纯实现是比较简单的,题目难点在于存取时间复杂度的要求是 O(1)

二、实现原理

主要是数据结构的选取,我们可以简单来分析下:

首先存数据,时间复杂度为 O(1),如果是简单的追加数据,链表和数组都可以,但因为需要体现“最近访问”,所以很大可能需要移动数据,那这时候数组就不是很适合了,链接倒是一个不错的选择

其次取数据,数组按下标取出,时间复杂度确实是 O(1),但显然我们这里是根据 key 去取对应的 value,很容易想到 python 里的 dict 类型

综上,我们采用的是链表 + 字典的组合。

链表存数据,字典也存在数据,这样显然会有很多问题,比如怎么快速根据 key 找到对应的链表?因此我们换一种思路,链表存取数据,包括key 和 value,而字典格式为 {key: node},即 key 和 对应的链表结点,这样就符合题目要求了

三、呈上代码

下面的实现还是有点不科学,首结点和尾结点没有用到循环链表(因为一开始指针问题思考错误,所以没有科学用于循环链表),但还是实现了,勉强可看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class CircleLinkNode:
    """
    双向链接,最先访问的放至尾部
    """
    def __init__(self, key, value):
        self.value = value
        self.key = key
        self.previous = None
        self.next = None


    def insert(self, node):
        """
        尾部插入
        """
        self.next = node
        node.previous = self

    def remove_to_end(self, last_node, root_node):
        """
        将当前结点移至最后
        """
        # 如果当前结点是尾部结点,直接return
        if self.next is None:
            return

        # 如果是头部结点
        if self.previous is None:
            root_node.next = self.next
            self.next.previous = None
            self.next = None
            self.previous = last_node.previous
            last_node.previous.next = self
            last_node.previous = self
            return

        self.previous.next = self.next
        self.next.previous = self.previous      
        self.previous = last_node.previous
        last_node.previous.next = self
        self.next = None
        last_node.previous = self


class LRUCache:

    def __init__(self, capacity: int):
        self._root_node = CircleLinkNode(None, None)  # 头结点
        self._cnode = CircleLinkNode(None, None)  # 当前结点
        self._res_dict = {}
        self._cur_len = 0
        self._capacity = capacity

    def get(self, key: int) -> int:
        # 更新链表结构
        cur_node = self._res_dict.get(key)
        if cur_node:
            cur_node.remove_to_end(self._cnode, self._root_node)
            return cur_node.value
        return self._res_dict.get(key, -1)

    def put(self, key: int, value: int) -> None:
        # 如果当前不存在值
        if self._cur_len == 0:
            new_node = CircleLinkNode(key, value)
            self._cnode.previous = new_node
            self._root_node.next = new_node
            self._res_dict[key] = new_node
            self._cur_len += 1
            return

        # 如果put的值在缓存中存在
        cur_node = self._res_dict.get(key)
        if cur_node:
            # 将当前结点移至尾部结点
            cur_node.value = value
            cur_node.remove_to_end(self._cnode, self._root_node)
            return

        # 如果put的值不在缓存中不存在并且长度不饱和
        if self._cur_len < self._capacity:
            self._cur_len += 1
            new_node = CircleLinkNode(key, value)
            self._cnode.previous.insert(new_node)
            self._cnode.previous = new_node
            self._res_dict[key] = new_node
            return

        # 如果put的值不在缓存中,且长度饱和
        # 先向后追加
        new_node = CircleLinkNode(key, value)
        self._cnode.previous.insert(new_node)
        self._cnode.previous = new_node
        temp_node = self._root_node.next
        self._res_dict[key] = new_node

        self._root_node.next = temp_node.next
        temp_node.next.previous = None
        temp_node.next = None  
        del self._res_dict[temp_node.key]
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FluentStudy 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
每日三题-合并K个升序链表、二叉树展开为链表、LRU缓存
👨‍💻个人主页: 才疏学浅的木子 🙇‍♂️ 本人也在学习阶段如若发现问题,请告知非常感谢 🙇‍♂️ 📒 本文来自专栏: 算法 🌈 算法类型:Hot100题 🌈 每日三题 合并K个升序链表 二叉树展开为链表 LRU缓存 合并K个升序链表 解法一 仿照两个升序链表合并一直循环 使用一个新节点res保存最终链表的头节点 然后循环遍历ListNode数组来与res来进行合并 时间复杂度 class Solution { public ListNode mergeKList
才疏学浅的木子
2022/11/13
2150
每日三题-合并K个升序链表、二叉树展开为链表、LRU缓存
手写双向循环链表+LRU练习
这里我们将循环链表中的结点值采用key与val存储。其余的就比较easy了,相信看完非常容易理解。
公众号guangcity
2020/07/02
4390
2020-09-18:LRU手撸,说下时间复杂度和空间复杂度。
空间复杂度:O(capacity),因为哈希表和双向链表最多存储 capacity+1 个元素。
福大大架构师每日一题
2020/09/18
1.1K0
2020-09-18:LRU手撸,说下时间复杂度和空间复杂度。
可视化图解算法:链表中的节点每k个一组翻转(反转)
将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表。 如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样。 你不能更改节点中的值,只能更改节点本身。
用户11589437
2025/03/31
190
可视化图解算法:链表中的节点每k个一组翻转(反转)
Python笔记题编程题答案
最近刚到一个新公司上班,比较忙,不能每天分享文章,希望大家见谅。我会尽最大努力找时间进行分享。
用户7685359
2020/08/24
6460
Python笔记题编程题答案
Python第九周 学习笔记(1)
描述器 ---- get(self, instance, owner) 访问属性时调用 set(self, instance, value) 当对属性赋值时调用 delete(self, instance) 删除属性时调用 self指代当前实例 instance是owner的实例 owner是属性的所属的类 描述器实现前提是描述器类实例作为类属性 当只实现get时 (非数据描述符),属性查找顺序是本实例优先,get方法次之 当实现get和set时(数据描述符) ,属性查找顺序是get方法优先 本
py3study
2020/01/10
5830
文心一言 VS 讯飞星火 VS chatgpt (113)-- 算法导论10.2 5题
在Go语言中,我们首先需要定义一个Node结构体来表示单向循环链表的节点,然后再定义一个LinkedList结构体来表示单向循环链表。接下来,我们可以实现INSERT、DELETE和SEARCH操作。为了给出运行时间,我们需要使用Go的"time"包来测量执行时间。
福大大架构师每日一题
2023/10/23
2290
文心一言 VS 讯飞星火 VS chatgpt (113)-- 算法导论10.2 5题
单链表实现LRU缓存淘汰算法
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是如果数据最近被访问过,那么将来被访问的几率也更高,相反如果很长时间未被访问,则它在最近一段时间内也不会被访问。实现方式有很多种,这里先介绍基于数组和单链表的实现方式。
WindCoder
2020/02/10
5790
数据结构与算法思想
分治法是基于多项分支递归的一种很重要的算法范式。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
知一
2021/12/07
4580
数据结构与算法思想
【Python数据结构与算法】线性结构小结
栈Stack维持了数据项后进先出LIFO的次序 stack的基本操作包括push,pop,isEmpty
ImAileen
2024/01/18
1820
【Python数据结构与算法】线性结构小结
力扣146.LRU 缓存机制
小长假来了,你有没有计划趁这个时间去做点什么呢?这里阿巩分享一个治疗拖延症自用的方法,我叫它“5分钟治疗法”,当你将要决定拖延时告诉自己现在开始做只要5分钟就好,你会发现一旦你投入到这件事的研究中就远不止5分钟了,会越来越欲罢不能~
才浅Coding攻略
2022/12/12
2300
力扣146.LRU 缓存机制
89 次荣登活跃榜,最高排名第 9 ,从零学算法第二周周报发布
当搜索一个键时,哈希表使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。
double
2020/06/12
7170
如何实现LRU缓存?请解释你的算法和数据结构选择
LRU(Least Recently Used)缓存是一种常见的数据结构,广泛应用于内存管理和资源调度中,目的是保持一个固定大小的缓存,并且在缓存达到最大容量时,移除最久未被访问的元素。本文将详细介绍LRU缓存的实现方法,重点讲解所使用的算法和数据结构,并通过代码示例让小白用户能轻松理解。
默 语
2025/05/21
2310
可视化图解算法:合并k个已排序(升序)的链表
数据范围:节点总数满足 0≤n≤105,链表个数满足 1≤k≤105 ,每个链表的长度满足 1≤len≤200 ,每个节点的值满足∣val∣<=1000
用户11589437
2025/03/31
990
可视化图解算法:合并k个已排序(升序)的链表
可视化图解算法:反转链表
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
用户11589437
2025/03/31
400
可视化图解算法:反转链表
Python带你了解数据结构【一】
我们学过计算机的童鞋们都知道算法与数据结构一直是大家逃不掉的噩梦,那么今天小编就带大家来看看用python来解读这些数据结构是否会变得简单一点呢?
我被狗咬了
2020/06/24
4870
LRU算法原理解析
LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。
py3study
2020/01/15
1.4K0
想进大厂、想提升底层功力、想搞懂LRU缓存,这一篇就够了!
历史题目:Ceph MDS 路径解析场景: 输入:/mnt/data/project1/report.docx 输出:逐级路径 ["mnt", "data", "project1", "report.docx"]
早起的鸟儿有虫吃
2025/07/10
2420
想进大厂、想提升底层功力、想搞懂LRU缓存,这一篇就够了!
Python 实现单向循环链表
循环链表的概念 1.什么是循环链表   所谓的循环链表就是让单向链表的首尾相连,组成一个环状。 2.循环链表的典型应用   约瑟夫环问题。 3.实现循环链表的重点   1,循环链表在插入第一个元素的时候,需要我们将第一元素的指针域指向其自身,也就构成了循环链表。   2,循环链表基于单向链表而生,单是比循环链表多了游标这个概念。要想实现循环链表的插入,删除的关键是考虑头结点问题,因为在头插法方式(往链表的头部插入数据)中,需要将末尾数据元素的指针域指向新插入的节点。将新插入的节点的指针域指向头结点的指针域的
YingJoy_
2018/04/28
1.5K0
实现LRU算法
计算机的缓存容量有限,如果缓存满了就要删除一些内容给新的内容腾出位置,而删除哪些内容,就有不同的策略,LRU算法是其中一种策略。
Defu Li
2020/09/08
8990
推荐阅读
相关推荐
每日三题-合并K个升序链表、二叉树展开为链表、LRU缓存
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档