[算法题]LRU 缓存机制

694 阅读3分钟

146.LRU 缓存机制

运用你所掌握的数据结构,设计和实现一个 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和链表节点的映射。

image.png

先定义一个双向链表,包括虚头节点和虚尾节点
当有put操作时,取消end节点的上一个节点的next指针指向,取消end节点的pre指针指向。
并将end节点的上一个节点的next指针指向新put进来的key-value创建的新节点,end节点的pre指针指向该新节点;
同时新节点的pre指针也要指向end节点的上一个节点,新节点的next指针指向end节点。
这样就完成了一次链表尾部添加新节点的操作

image.png

当已存在的数据被访问时,需要将链表对应节点与前后节点断开,前后节点互相连接,再将对应的节点添加进虚尾节点的前面。

image.png

代码如下

/**
 * @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🖖.