零 题目:算法(leetcode,附思维导图 + 全副解法)300 题之(146)LRU 缓存
一 题目形容
二 解法总览(思维导图)
三 全副解法
1 计划 1
1)代码:
// 计划 1“本人。哈希法”。// 技巧:遇到 O(1) 的 get、put 操作,优先思考 哈希表(JS 里的 Map 数据结构)。/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
this.capacity = capacity;
this.curCapacity = 0;
// 注:越是最近应用的 key,就越往 map 的前面放!this.map = new Map();};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {const {map} = this;
let resVal = -1;
// 边界 1:get 操作,若 曾经有该 key,// 则 先删该 key、接着把该 key 放到 map 的最初 —— 示意最近应用过该 key!if (map.has(key)) {resVal = map.get(key);
map.delete(key);
map.set(key, resVal);
}
return resVal;
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {const {capacity, curCapacity, map} = this;
// 边界 2:put 操作,若 曾经有该 key,// 则 先删该 key、接着把该 key 放到 map 的最初 —— 示意最近应用过该 key!if (map.has(key)) {map.delete(key);
map.set(key, value);
return;
}
// 边界 3:put 操作,若 以后曾经放不下了(即 curCapacity >= capacity),// 则 删除 map 的第一个 key 且 将新 key 放到 map 的最初 —— 示意最近应用过该 key!if (curCapacity >= capacity) {for (const [key, val] of map) {map.delete(key);
break;
}
map.set(key, value);
}
// 边界 4:put 操作,若 以后放得下(即 curCapacity < capacity),// 则 间接将新 key 放到 map 的最初 —— 示意最近应用过该 key 且 this.curCapacity++。else {map.set(key, value);
this.curCapacity++;
}
// 注:以上 5 行,能够合并成如下 2 行(但为了可读性,可分拆为 if、else 2 分支):// }
// map.set(key, value);
};
2 计划 2
1)代码:
// 计划 2“本人。哈希表 + 双向链表”。// 参考:// 1)https://leetcode.cn/problems/lru-cache/solution/146-lru-huan-cun-ji-zhi-by-chen-wei-f-tl1n/
// 思路:// 1)重点实现 ListNode 和 LRUCache 2 个类。// 2)get、put 操作:// 依据当前情况进行 curCapacity、map、各种节点 的信息变更即可,详见代码的实现。// 双向链表的单个节点
class ListNode {constructor(key, value) {
this.key = key;
this.value = value;
// 指向后一个节点
this.next = null;
// 指向前一个节点
this.prev = null;
}
}
class LRUCache {constructor(capacity) {
this.capacity = capacity;
this.curCapacity = 0;
this.map = new Map();
this.dummyHead = new ListNode();
this.dummyTail = new ListNode();
this.dummyHead.next = this.dummyTail;
this.dummyTail.prev = this.dummyHead;
}
get(key) {const {map = new Map()} = this,
node = map.get(key);
// 没找到
if (!node) {return - 1;}
// 找到,将该节点挪到头部,并返回相应的值
this.moveToHead(node);
return node.value;
}
put(key, value) {const {map = new Map()} = this,
node = map.get(key);
if (!node) {
// 不存在该节点,则进行节点的创立。const newNode = new ListNode(key, value);
map.set(key, newNode);
// 退出头结点(addToHead),而不是挪(moveToHead)this.addToHead(newNode);
this.curCapacity++;
// 边界:超容量了,得删除
if (this.curCapacity > this.capacity) {this.removeLRUItem();
}
}
else {
// 存在该节点,更新其值 并 将该节点挪到头部
node.value = value;
this.moveToHead(node);
}
}
moveToHead(node) {
// 从链表中删除该节点 并 将该节点增加至头结点
this.removeFromList(node);
this.addToHead(node);
}
// 节点的删除(实质:指针指向的变更)removeFromList(node) {
let nodePre = node.prev,
nodeNext = node.next;
nodePre.next = nodeNext;
nodeNext.prev = nodePre;
}
// 节点退出链表头部 的指针操作
addToHead(node) {const {dummyHead = null} = this,
dummyHeadNext = dummyHead.next;
node.prev = dummyHead;
node.next = dummyHeadNext;
dummyHeadNext.prev = node;
dummyHead.next = node;
// 注:下面 4 行代码等价于上面 1 行(JS 的语法糖)// [node.prev, node.next, dummyHeadNext.prev, dummyHead.next] = [dummyHead, dummyHeadNext, node, node];
}
removeLRUItem() {const {dummyTail = null, map = new Map()} = this,
tailNode = dummyTail.prev;
// 删除尾结点 并更新容量(即 curCapacity)信息
this.removeFromList(tailNode);
map.delete(tailNode.key);
this.curCapacity--;
}
}
四 资源分享 & 更多
1 历史文章 – 总览
2 博主简介
码农三少,一个致力于编写 极简、但齐全题解(算法 )的博主。
专一于 一题多解、结构化思维,欢送一起刷穿 LeetCode ~