【leetcode】146LRU缓存
题目
请你设计并实现一个满足 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) 的平均时间复杂度运行。
思路
操作系统中学习过LRU的相关知识,中文是最长最久未使用,是页面置换算法的一部分。即页表在置换算法的时候,从当前时刻往前找,找到以载入页面中最长时间没有被使用的哪一个,将其置换出去。
对于代码而言,可以用双向链表+哈希表的结构来实现。
- 哈希表存放key和node的关系;
- node中存放key和value(注意get的时候需要返回的是value);
我们使用list来维护节点被使用的时间,一个节点被访问就插入到链表头,这样就能保证链表尾部的就是那个最久没有被使用的节点。链表元素超出capacity需要删除节点的时候,就将尾部的节点删除即可。
哈希表是用来判断某一个节点是否已经存在于链表中的,链表的查找时间复杂度是O(N)
,哈希表能将其降至O(1)
;注意,删除某个节点的时候也需要将这个节点从哈希表中剔除,调用erase函数即可。
如果一个key已经存在与list中,访问的时候就需要将其先删除,再头插,相当于更新了被使用的时间。为了满足题目中O(1)
时间复杂度的要求,使用双向链表才能保证删除的时间复杂度是O(1)
。
get
get操作的思路如下
- 查询需要的key是否存在于哈希表中,如果不存在直接返回
-1
; - 如果存在,则获得该key对应的链表节点,并从节点中读取value值(返回值);
- 如果存在,将对应节点从链表中删除,再头插,以更新访问时间。
- 因为删除和插入操作用的都是原来的节点,key和节点地址的映射关系没有变化,所以不需要修改哈希表,也不需要修改size。
- 返回刚刚记录的存在节点的value值。
put
put操作的思路如下
- 查询需要的插入的key是否存在于哈希表中,如果存在则和get操作类似,将其从链表中删除再头插,注意需要修改node节点中的value。
- 需要的key不在哈希表中,则判断当前长度
- 未超出限制,new一个新的链表节点,头插到链表中,并将节点值和节点地址的映射关系插入哈希表,size+1;
- 链表已有长度等于最大值了,尾删节点并删除该节点值在哈希表中的映射;new一个新的链表节点,头插到链表中,并将节点值和节点地址的映射关系插入哈希表。因为删除又新增了一个节点,size不变。
这样就能保证链表中的节点是按最近访问(get或者put)的时间来按顺序排列的,末尾节点是最久没有被操作过的节点。
代码
刚开始用的头删+尾插的方式,结果弄了好久都还有段错误,改成头插+尾删好处理一些。
为了方便处理头节点和尾部节点的插入和删除,在初始化链表的时候会初始化一个head和tail节点,这样后序新增/删除节点的所有操作都能统一处理。
刚开始我作死用了双向循环链表+单头节点,结果写了好久都没把链表头插和尾删搞明白,只能说自己实在是太菜了。还是用现在这种方式更好控制一些。
1 | struct Node |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 慕雪的寒舍!
评论