[路飞]_程序员必刷力扣题: 面试题 16.25. LRU 缓存

826 阅读3分钟

「这是我参与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 次 putget 和 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--
};

谢谢大家,一起加油