LRUCache是一个比较典型的面试题,不管是前端面试还是后台面试,很大概率都会遇到,这里给出一个基本实现。
问题
- 实现一个缓存,缓存有一个容量大小为capacity,缓存数据以key/value形式存储。
- 当缓存的数据超过capacity的时候,将按照LRU(Least Recent Used, 最近最少使用)规则进行删除,从而腾出空间用于存储新的key/value数据;
- 缓存实现get和set操作,并且平均时间复杂度为O(1)
实现原理
- 用一个Map类型来存储缓存数据的key/value,其中的key为需要存储的key,但value需要封装下,这里为一个链表节点(没错,需要用到双向链表);
- 用双向链表保存所的key/value数据结构。其中,
链表头保存的是最近最少访问的节点(即LRU节点),链表尾保存的是最近最常访问的节点; - 如果是get操作,则通过Map,获取对应的链表节点,同时将该节点移动到链表尾部(最近最常访问);
- 如果是put操作,则判断是否当前达到了capacity。如果达到了capacity,则首先移除链头节点,然后再在链尾插入新节点;如果没有达到capacity,则直接在链尾插入新节点。
具体实现
定义双向链表节点
class LRUNode {
key: number;
value: number;
next: LRUNode | null;
prev: LRUNode | null;
constructor(key: number, value: number) {
this.key = key;
this.value = value;
this.next = null;
this.prev = null;
}
}
定义LRUCache类
基本结构如下
class LRUCache {
capacity: number;
cache: Map<number, LRUNode>;
head: LRUNode | null;
tail: LRUNode | null;
constructor(capacity: number) {
this.capacity = capacity;
this.cache = new Map<number, LRUNode>();
this.head = this.tail = null;
}
get(key: number): number {
}
put(key: number, value: number): void {
}
}
实现链表的基本操作
1. 实现删除节点
class LRUCache {
// 其他代码
...
// 删除节点
_remove(node: LRUNode): void {
const preNode = node.prev;
const nextNode = node.next;
if (preNode) {
preNode.next = nextNode;
}
if (nextNode) {
nextNode.prev = preNode;
}
// 如果删除的node为head
if (node === this.head) {
this.head = node.next;
this.head && (this.head.prev = null);
}
// 如果删除的node为tail
if (node === this.tail) {
this.tail = node.prev;
this.tail && (this.tail.next = null);
}
}
}
2. 添加节点到尾部
class LRUCache {
// 其他代码
...
// 添加节点到尾部
_addToTail(node: LRUNode): void {
if (!this.tail) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
this.tail.next = null;
}
}
}
3. 移动节点到尾部
class LRUCache {
// 其他代码
...
// 移动节点到尾部
_moveToTail(node: LRUNode): void {
this._remove(node);
this._addToTail(node);
}
}
实现get操作
class LRUCache {
// 其他代码
...
get(key: number): number {
if (this.cache.has(key)) {
// 存在对应的key
const node: LRUNode = this.cache.get(key)!;
this._moveToTail(node);
return node.value;
} else {
// 不存在对应的key
return -1;
}
}
}
实现put操作
class LRUCache {
// 其他代码
...
put(key: number, value: number): void {
if (this.cache.has(key)) {
/**
* 存在对应的key
* 1. 更新对应节点的value
* 2. 将节点移动到链表尾部
*/
const node: LRUNode = this.cache.get(key)!;
node.value = value;
this._moveToTail(node);
} else {
const node: LRUNode = new LRUNode(key, value);
if (this.cache.size < this.capacity) {
/**
* 容量没有超过capacity
* 直接将新节点添加到链表尾部
*/
this._addToTail(node);
} else {
/**
* 容量操作capacity
* 1. 删除头部节点
* 2. 将新节点添加到链表尾部
* 3. 删除cache中对应的key
*/
const headNode = this.head!;
this._remove(headNode);
this._addToTail(node);
this.cache.delete(headNode.key);
}
this.cache.set(key, node);
}
}
}