LeetCode-in-All

146. LRU Cache

Medium

Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.

Implement the LRUCache class:

The functions get and put must each run in O(1) average time complexity.

Example 1:

Input [“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]

Output: [null, null, null, 1, null, -1, null, -1, 3, 4]

Explanation:

LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // cache is {1=1}
lRUCache.put(2, 2); // cache is {1=1, 2=2}
lRUCache.get(1); // return 1
lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
lRUCache.get(2); // returns -1 (not found)
lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
lRUCache.get(1); // return -1 (not found)
lRUCache.get(3); // return 3
lRUCache.get(4); // return 4 

Constraints:

Solution

interface ICacheNode {
    key: number
    value: number
    prev: ICacheNode | null
    next: ICacheNode | null
}

class CacheNode implements ICacheNode {
    public key: number
    public value: number
    public prev: ICacheNode
    public next: ICacheNode

    constructor(key: number, value: number, prev?: ICacheNode, next?: ICacheNode) {
        this.key = key
        this.value = value
        this.prev = prev ?? null
        this.next = next ?? null
    }
}

class LRUCache {
    private cache = new Map<number, CacheNode>()
    private capacity
    private head = new CacheNode(0, 0)
    private tail = new CacheNode(0, 0)

    constructor(capacity: number) {
        this.capacity = capacity
        this.head.next = this.tail
        this.tail.prev = this.head
    }

    private append(node: CacheNode) {
        const prev = this.tail.prev
        this.tail.prev = node
        node.next = this.tail
        node.prev = prev
        prev.next = node
    }

    private remove(node: CacheNode) {
        const { prev, next } = node
        prev.next = next
        next.prev = prev
        node.next = null
        node.prev = null
        return node
    }

    private promote(node: CacheNode) {
        const removed = this.remove(node)
        this.append(removed)
    }

    get(key: number): number {
        if (!this.cache.has(key)) {
            return -1
        }
        const node = this.cache.get(key)
        this.promote(node)
        return node.value
    }

    put(key: number, value: number): void {
        let node
        if (this.cache.has(key)) {
            node = this.cache.get(key)
            node.value = value
            this.promote(node)
        } else {
            if (this.capacity === this.cache.size) {
                const leastUsed = this.head.next
                this.cache.delete(leastUsed.key)
                this.remove(leastUsed)
            }
            node = new CacheNode(key, value)
            this.append(node)
        }
        this.cache.set(key, node)
    }
}

/*
 * Your LRUCache object will be instantiated and called as such:
 * var obj = new LRUCache(capacity)
 * var param_1 = obj.get(key)
 * obj.put(key,value)
 */

export { LRUCache }