LeetCodeLRU缓存 - 最近最少使用缓存机制 - JavaScript描述 - Map - 双向链表
Posted YK菌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCodeLRU缓存 - 最近最少使用缓存机制 - JavaScript描述 - Map - 双向链表相关的知识,希望对你有一定的参考价值。
嗨!~ 大家好,我是YK菌 🐷 ,一个微系前端 ✨,爱思考,爱总结,爱记录,爱分享 🏹,欢迎关注我呀 😘 ~ [微信号:
yk2012yk2012
,微信公众号:ykyk2012
]
先说说我与这道题的缘分吧~ 第一次是去哪儿的一面,面试官问我知道LRU缓存吗,让我实现一个javascript版本的… 我说我听过,但是我可能不太会实现,然后就给我换成简单题反转链表了。虽然写出来简单题了,但是反手还是给我挂了~ 第二次遇到是快手二面,面试官问我知道LRU缓存吗? 我说我知道一点,然后他说你不知道没事,我告诉你,就给我解释了一下什么是LRU,然后告诉我我要实现什么功能,然后在面试官的指导下我就给做出来了~ 当然最后这轮面试也通过了~
146. LRU 缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现LRUCache
类:
LRUCache(int capacity)
以正整数作为容量 capacity 初始化 LRU 缓存int get(int key)
如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1void put(int key, int value)
如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间
示例
- 输入
["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]
-
解释
1. LRUCache lRUCache = new LRUCache(2);
2. lRUCache.put(1, 1); // 缓存是 1=1
3. lRUCache.put(2, 2); // 缓存是 1=1, 2=2
4. lRUCache.get(1); // 返回 1
5. lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 1=1, 3=3
6. lRUCache.get(2); // 返回 -1 (未找到)
7. lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 4=4, 3=3
8. lRUCache.get(1); // 返回 -1 (未找到)
9. lRUCache.get(3); // 返回 3
10. lRUCache.get(4); // 返回 4
分析一下,实现最近最少使用缓存机制。这里有两个操作,一个是
get
读数据,一个是put
写数据或更新数据。这个缓存的容量是有限的,也就是说数据写(put
)多了会删掉老
的数据,何为老
的数据呢,就是最久没有被读过(get
)或者更新(put
)的数据,换句话说,读过或者更新过的数据会变新
。
接下来就是要选用哪一种数据结构了,我们可以想象如果这些数据从来没有被读取过,那这是不是就相当于是一个队列
然后就是如果get读取了数据,数据会变新
,体现出来的就是移动到队列的最右侧
好,下面仔细分析读取和写入操作
get
读取数据 : 在缓存中查找位置,找到的话,将这个数据移动到最右侧并返回该数据;没有找到的话,返回-1
put
写入数据 : 在缓存中查找位置,有的话,执行更新操作,更新值并将这个数据移动到最右侧; 没有找到的话,执行添加操作,就在最右侧添加新数据;这里添加操作又分两种情况,一是容量还没有满,就直接添加,二是容量已满,就执行删除操作,删除最左侧的数据。
这里涉及的数据的操作有:
① 查找
涉及到键值对,还有查找,比较容易想到的就是用一个js中的对象
来存储键值对
② 移动
涉及数据频繁移动的数据结构我们想到的就是链表
③ 删除
删除链表一侧的节点,我们想到的就是双向链表
最后我们设计的数据结构应该是这样的
关于双向链表的实现可以参看这篇博文 【LeetCode】设计链表II —— JavaScript实现双向链表 - 掘金 (juejin.cn)
这样,删除、添加、移动数据的操作,都是改变链表的各种指针即可~
【解法一】 双向链表
首先定义一个双向链表中的节点类,节点存储键和值两个数据
class ListNode
constructor(key, value)
this.key = key
this.value = value
this.next = null
this.prev = null
然后开始定义我们的定义 LRU 缓存机制
class LRUCache
// 缓存构造函数
constructor(capacity)
// 设置缓存容量,用来存储键值对的空对象 以及 存储当前缓存中数据的数量
this.capacity = capacity;
this.hash = ;
this.count = 0;
// 定义双向链表的虚拟头节点和虚拟尾节点
this.dummyHead = new ListNode();
this.dummyTail = new ListNode();
// 将节点关联起来
this.dummyHead.next = this.dummyTail;
this.dummyTail.prev = this.dummyHead;
// 下面开始定义一些缓存方法
// get操作,获取元素
get(key)
// 直接从对象中获取这个节点
let node = this.hash[key];
// 找不到就返回-1
if (node === null)
return -1;
else
// 找到了,就移动节点到链表头部(删除这个节点,然后添加到头部)
this.removeFromList(node);
this.addToHead(node);
// 然后返回此节点的值
return node.value;
put(key, value)
// 现在对象中找这个节点
let node = this.hash[key];
// 找不到就进行添加操作
if (node == null)
// 如果缓存满了,就删除尾节点
if (this.count == this.capacity)
let tail = this.dummyTail.prev;
this.removeFromList(tail);
// 删除对象中的映射关系
delete this.hash[tail.key];
// 缓存的数据数量减一
this.count--;
// 如果缓存没有满,就创建一个新的节点
let newNode = new ListNode(key, value);
// 在对象中建立映射
this.hash[key] = newNode;
// 添加的链表头部
this.addToHead(newNode);
// 缓存的数据数量加一
this.count++;
else
// 找到了就执行【更新操作】
node.value = value;
this.removeFromList(node);
this.addToHead(node);
// 从链表中删除节点的操作
removeFromList(node)
let temp1 = node.prev;
let temp2 = node.next;
temp1.next = temp2;
temp2.prev = temp1;
// 把节点添加到链表头部的操作
addToHead(node)
node.prev = this.dummyHead;
node.next = this.dummyHead.next;
this.dummyHead.next.prev = node;
this.dummyHead.next = node;
【解法二】Map
在JavaScript中借助Map这个数据结构里面的一些API,我们可以很容易就实现出一个 LRU 缓存
Map与Object类型的一个主要差异是,Map实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作。
keys()和values()分别返回以插入顺序生成键和值的迭代器
如何移动一个元素到顶部呢,和我们上面用双链表实现是一样的逻辑,直接删除这个元素,然后重新插入它
class LRUCache
constructor(capacity)
this.capacity = capacity;
this.map = new Map();
get(key)
if (this.map.has(key))
let temp = this.map.get(key);
// 将元素从map中删除
this.map.delete(key);
// 然后重新插入到map中
this.map.set(key, temp);
return temp;
else
return -1;
put(key, value)
if (this.map.has(key))
this.map.delete(key);
this.map.set(key, value);
if (this.map.size > this.capacity)
// 删除最“老”的节点,也就是最先插入元素,map.keys产生的是一个迭代器,所以使用next可以获取第一个元素
this.map.delete(this.map.keys().next().value);
你猜,我面试的时候写的是哪个版本?
最后,欢迎关注我的专栏,和YK菌做好朋友
以上是关于LeetCodeLRU缓存 - 最近最少使用缓存机制 - JavaScript描述 - Map - 双向链表的主要内容,如果未能解决你的问题,请参考以下文章