运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。 实现 LRUCache 类:
LRUCache(int capacity)
以正整数作为容量capacity
初始化LRU
缓存int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回-1
。void 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]
题意理解
这道题意思是,创建一个指定容量的cache对象(可以想象成队列),通过put向cache中存值,容量满了按照先进先出的规则去除最先进来的值,中间如果有对某一值进行访问,则该值移至队尾;若访问不到对应的值,则返回-1。
// 给了初始代码
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {
};
数组+Map实现
数组用来有序存储key,Map用来存储key-value。当put时,判断key是否已经存在,若是,在数组中将该key移至队尾;
若数组容量溢出,将队头元素剔除,同时删除Map中对应的key-value;然后添加当前put的key-value到Map中。
代码如下
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
// 保存容量大小
this.capacity = capacity;
this.que = [];
this.map = new Map();
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
const res = this.map.get(key);
if (res === undefined) return -1;
// 将key移到队尾
const index = this.que.findIndex(value => value === key);
this.que.splice(index, 1);
this.que.push(key);
return res;
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {
// 存在key时,将key移到队尾
if (this.map.get(key)) {
for(let i = 0; i<this.capacity; i++) {
if (this.que[i] === key) {
this.que.splice(i, 1);
}
}
}
this.que.push(key);
// 容量满了,剔除队头key和map对应的key-value
if (this.que.length > this.capacity) {
const deleteKey = this.que.shift();
this.map.delete(deleteKey)
}
this.map.set(key, value);
};
通过使用数组的方式,很容易的实现了,但在查找key位于队列中的下标需要遍历,时间复杂度O(n)
;
题目有进阶问题:进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
双向链表+Map
使用双向链表按被使用顺序存储key-value,Map则用来对key和链表节点的映射。
先定义一个双向链表,包括虚头节点和虚尾节点
当有put操作时,取消end节点的上一个节点的next指针指向,取消end节点的pre指针指向。
并将end节点的上一个节点的next指针指向新put进来的key-value创建的新节点,end节点的pre指针指向该新节点;
同时新节点的pre指针也要指向end节点的上一个节点,新节点的next指针指向end节点。
这样就完成了一次链表尾部添加新节点的操作
当已存在的数据被访问时,需要将链表对应节点与前后节点断开,前后节点互相连接,再将对应的节点添加进虚尾节点的前面。
代码如下
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
this.capacity = capacity;
this.vhead = new Node(null);
this.vend = new Node(null);
this.vhead.next = this.vend;
this.vend.pre = this.vhead;
this.map = new Map();
};
class Node {
constructor(value){
this.next = null;
this.pre = null;
this.val = value;
}
}
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
const res = this.map.get(key);
if (res === undefined) return -1;
if (this.length === 1) return res.val.value;
res.pre.next = res.next;
res.next.pre = res.pre;
this.vend.pre.next = res;
res.pre = this.vend.pre
res.next = this.vend;
this.vend.pre = res;
return res.val.value;
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {
const node = this.map.get(key);
if (node) {
node.pre.next = node.next;
node.next.pre = node.pre;
this.map.delete(node.val.key)
} else {
if (this.map.size === this.capacity) {
const first = this.vhead.next;
first.pre.next = first.next;
first.next.pre = this.vhead
this.map.delete(first.val.key)
}
}
const newNode = new Node({
value,
key
});
this.vend.pre.next = newNode
newNode.pre = this.vend.pre
newNode.next = this.vend;
this.vend.pre = newNode;
this.map.set(key, newNode);
};
~~~~~end🖖.