「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战」
面试题 16.25. LRU 缓存
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
提示:
0 <= key, value <= 10^6- 最多调用
104次put、get和remove方法
利用数组实现
思路
题目中要求我们实现一个LRU 缓存,
- 有一个初始长度,保存元素的方式为一个数组[key,value,time]
- get:获取保存的元素,如果存在key返回对应的value,否则返回-1
- put:保存元素key,value
- 如果key存在则更新key对应的value
- 如果key不存在
- 如果缓存长度未满,则直接push[key,value,time]
- 如果缓存长度已满,需要删除最近最少使用的元素,然后塞入新的元素
具体实现:
- 初始化元素,我们需要一个记录时间节点的time,保存缓存长度capacity,初始化一个数组用来保存缓存Cache
- get: 获取元素,通过遍历缓存,判断是否存在key,存在则返回对应item[1]的值,并且重置该元素的时间item[2]=this.time,否则返回-1
- put: 保存缓存,遍历数组查找key
- 如果存在key,则更新value值以及对应的time
- 如果不存在key
- 缓存数组未满,则直接push元素[key,value,time]
- 缓存数组已满,则需要按照time进行降序,删除末尾元素,因为该元素是time最小的,也就是最近最少使用的元素,然后将新元素push进去即可
/**
* @param {number} capacity
*/
var LRUCache = function (capacity) {
this.time = 0
this.Capacity = capacity
this.Cache = []
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function (key) {
this.time++
var res = -1
for (var it of this.Cache) {
if (it[0] === key) {
res = it[1]
it[2] = this.time
return res
}
}
// console.log('get', this.Cache)
return res
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function (key, value) {
this.time++
for (var it of this.Cache) {
if (it[0] === key) {
it[1] = value
it[2] = this.time
return
}
}
if (this.Cache.length === this.Capacity) {
this.Cache.sort((a, b) => b[2] - a[2])
this.Cache.pop()
}
this.Cache.push([key, value, this.time])
// console.log('put', this.Cache)
};
哈希表+双链表
思路
为了以O(1)时间复杂度访问key,我们使用哈希表来保存
为了方便查找最近未使用的元素我们使用双向链表来保存元素
我们用Map函数来保存key和对应的链表节点值
链表的每一个节点包含四个属性,分别是key,value,prev,next
为了方便查找链表节点我们这里需要一个头结点head以及一个尾结点tail
具体实现:
- 初始化head。tail,记录当前size以及最大maxSize,map用来记录key对应的节点
- get: map中存在key,返回对应node的值value,否则返回-1
- DelNode: 删除一个节点,处理当前节点的prev节点以及next节点,map删除key,size--
- AddNode: 往头部新增一个节点,size++
- MoveHead:删除当前节点,然后往头部新增该节点
- put:添加元素
- 如果map存在对应key,则直接更新value,return 结束函数执行
- 不存在判断且size===maxSize
- 删除尾结点tail的前一个节点
- 创建新节点并AddNode 到这里完成LRU缓存
function DoubleList(key, value, prev, next) {
this.key = key || null
this.value = value || null
this.prev = prev || null
this.next = next || null
}
var LRUCache = function (capacity) {
this.head = new DoubleList('head', 'head')
this.tail = new DoubleList('tail', 'tail', this.head)
this.head.next = this.tail
this.map = new Map()
this.size = 0
this.maxSize = capacity
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function (key) {
// 存在
if (this.map.has(key)) {
var node = this.map.get(key)
this.MoveHead(node)
return node.value
}
// 不存在
return -1
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function (key, value) {
// 存在
if (this.map.has(key)) {
var node = this.map.get(key)
node.value = value
this.MoveHead(node)
return
}
// 不存在判断长度
if (this.size === this.maxSize) {
// 删除最后一个节点
var lastNode = this.tail.prev
// console.log('put',node)
this.DelNode(lastNode)
}
// 添加新元素
var newNode = new DoubleList(key, value)
this.AddNode(newNode)
};
LRUCache.prototype.AddNode = function(node){
var prev = this.head
var next = this.head.next
// 头
prev.next = node
// curr
node.next = next
node.prev = prev
// next
next.prev = node
// 新增map
this.map.set(node.key,node)
// size
this.size++
}
LRUCache.prototype.MoveHead = function (node) {
this.DelNode(node)
this.AddNode(node)
};
LRUCache.prototype.DelNode = function (node) {
var prev = node.prev
var next = node.next
var key = node.key
// 删除链表中的node
prev.next = next
next.prev = prev
// 删除map中的节点
this.map.delete(key)
// size
this.size--
};
谢谢大家,一起加油