面试题 16.25. LRU Cache LCCI

211 阅读2分钟

题目描述

leetcode-cn.com/problems/lr…

分析

实现 LRU,为实现快速的查找,删除,我会用哈希表和双向链表来完成实现

这是一道很好的考察数据结构的题目,涉及两个数据结构的实现

算法

哈希表,双向链表

过程

实现数据结构(hashSet,DoubleLinkedList) -> 实现方法

数据结构

对于哈希表:

class Hash {
    constructor() {
        this.base = 697
        this.data = new Array(this.base).fill(0).map(() => new Array())
    }
    
    set(key, value) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (const x of it) {
            if (x[0] === key) {
                x[1] = value
                return
            }
        }
        it.push([key, value])
    }
    
    get(key) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (const x of it) {
            if (x[0] === key) return x[1]
        }
        
        return -1
    }
    
    delete(key) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (let i = 0; i < it.length; i++) {
            if (it[i][0] === key) {
                it.splice(i, 1)
                return
            }
        }
    }
    
    contains(key) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (const x of it) {
            if (x[0] === key) {
                return true
            }
        }
        
        return false
    }
    
    hash(key) {
        return key % this.base
    }
}

双向链表

class ListNode {
    constructor(key, value) {
        this.key = key
        this.value = value
        this.prev = null
        this.next = null
    }
}

实现 LRU 方法

实例的数据

头节点,尾节点

头节点:dummyHead, 尾节点:dummyTail

在缓存过程中,一定涉及到过期数据的清理与新数据的插入,因此一个 LRU 实例一定会保存头节点,尾节点

首先将他们声明为空节点: new ListNode,在链接起来,使成为环形链表

容量

this.capacity 作为容量

缓存总量

this.cnt 作为已经存储的数据的数量

哈希表

this.hash 作为存储数据的哈希

方法

LRUCache.prototype.get

作用是拿到某个 key 的元素

从哈希中拿出元素 node,此时如果数据不存在的话值是 -1

存在:移动节点至头部,返回 node.value 不存在:返回 -1

LRUCache.prototype.moveToHead

我加的方法,作用是将某个节点移动到头部,即 this.dummyHead.next

(此处再次说明 dummyHead, dummyTail 的优势,没必要考虑现在链表里有多少个节点,不用判空)

函数体内再次调用了我自定义的两个方法

removeFromList,删除 node -> addToHead,将 node 移动到头部

两方法均为改变节点指针指向,不再赘述

LRUCache.prototype.put

作用是设置某个 keyvalue

查找 key 对应的节点,从 hash 中找到对应节点,此时有两种情况

不存在:如果判断插入后超过了 capacity,需要先删除一个尾节点, 否则直接将新节点插入链表并在 hash 记录,更新 cnt,请注意我实现的 addToHead 方法 存在:更新对应节点的 value,并调用 moveToHead 更新缓存记录

代码

/**
 * @param {number} capacity
 */
var LRUCache = function(capacity) {
    this.capacity = capacity
    this.hash = new Hash()
    this.cnt = 0
    this.dummyHead = new ListNode()
    this.dummyTail = new ListNode()
    this.dummyHead.next = this.dummyTail
    this.dummyTail.next = this.dummyHead
};

/** 
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function(key) {
    const node = this.hash.get(key)
    if (node === -1) return -1
    this.moveToHead(node)
    return node.value
};

/** 
 * @param {number} key 
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function(key, value) {
    const node = this.hash.get(key)
    if (node === -1) {
        if (this.capacity === this.cnt) this.removeLRUItem()
        let newNode = new ListNode(key, value)
      this.hash.set(key,  newNode) 
      this.addToHead(newNode)
        this.cnt++
    } else {
        node.value = value
        this.moveToHead(node)
    }
};

/** 
 * @param {ListNode} node 
 * @return {void}
 */
LRUCache.prototype.moveToHead = function(node) {
    this.removeFromList(node)
    this.addToHead(node)
};

/** 
 * @param {ListNode} node
 * @return {void}
 */
LRUCache.prototype.removeFromList = function(node) {
    const next = node.next
    const prev = node.prev
    prev.next = next
    next.prev = prev
};


/** 
 * @param {ListNode} node
 * @return {void}
 */
LRUCache.prototype.addToHead = function(node)  {                 // 插入到虚拟头结点和真实头结点之间
  node.prev = this.dummyHead      // node的prev指针,指向虚拟头结点
  node.next = this.dummyHead.next // node的next指针,指向原来的真实头结点
  this.dummyHead.next.prev = node // 原来的真实头结点的prev,指向node
  this.dummyHead.next = node      // 虚拟头结点的next,指向node
}

/** 
 * @return {void}
 */
LRUCache.prototype.removeLRUItem = function() {
    const tail = this.popTail()
    this.hash.delete(tail.key)
    this.cnt--
};

/** 
 * @return {void}
 */
LRUCache.prototype.popTail = function() {
    const tail = this.dummyTail.prev
    this.removeFromList(tail)
    return tail
};

class ListNode {
    constructor(key, value) {
        this.key = key
        this.value = value
        this.prev = null
        this.next = null
    }
}

class Hash {
    constructor() {
        this.base = 697
        this.data = new Array(this.base).fill(0).map(() => new Array())
    }
    
    set(key, value) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (const x of it) {
            if (x[0] === key) {
                x[1] = value
                return
            }
        }
        it.push([key, value])
    }
    
    get(key) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (const x of it) {
            if (x[0] === key) return x[1]
        }
        
        return -1
    }
    
    delete(key) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (let i = 0; i < it.length; i++) {
            if (it[i][0] === key) {
                it.splice(i, 1)
                return
            }
        }
    }
    
    contains(key) {
        const ind = this.hash(key)
        const it = this.data[ind]
        for (const x of it) {
            if (x[0] === key) {
                return true
            }
        }
        
        return false
    }
    
    hash(key) {
        return key % this.base
    }
}