【JavaScript面试题-算法与数据结构】手写一个 LRU(最近最少使用)缓存类,支持 `get` 和 `put` 操作,要求时间复杂度 O(1)

一、数据结构设计

为了实现 getput 操作的时间复杂度 O(1) ,我们组合了两种数据结构:

  1. 哈希表Map 或普通对象)

    • 存储键到双向链表节点的映射。
    • 作用:通过 key 直接定位到节点,实现 O(1) 的查找。
  2. 双向链表Node 类实现)

    • 维护所有节点的 使用顺序:链表头部(head 之后)是最近使用的节点,链表尾部(tail 之前)是最久未使用的节点。
    • 作用:在 O(1) 时间内完成节点的 移动到头部删除尾部 等操作。

此外,使用两个 哨兵节点 (伪头 head 和伪尾 tail),避免处理链表为空或只有一个节点时的边界条件,使插入和删除操作更简洁。

以下是 JavaScript 手写的 LRU 缓存类,使用哈希表 + 双向链表实现,确保 getput 操作时间复杂度为 O(1):

javascript

kotlin 复制代码
class LRUCache {
    /**
     * @param {number} capacity 缓存容量
     */
    constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map(); // 键 → 节点
        // 创建哨兵头尾节点,简化边界处理
        this.head = new Node(0, 0);
        this.tail = new Node(0, 0);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

    /**
     * 获取键对应的值,并将该节点移动到链表头部(最近使用)
     * @param {number} key
     * @return {number}
     */
    get(key) {
        if (!this.cache.has(key)) {
            return -1;
        }
        const node = this.cache.get(key);
        this._moveToHead(node);
        return node.value;
    }

    /**
     * 插入或更新键值对,并将节点置于头部(最近使用)
     * 若容量超限,删除尾部节点(最久未使用)
     * @param {number} key
     * @param {number} value
     * @return {void}
     */
    put(key, value) {
        if (this.cache.has(key)) {
            // 已存在:更新值并移到头部
            const node = this.cache.get(key);
            node.value = value;
            this._moveToHead(node);
        } else {
            // 不存在:新建节点
            if (this.cache.size === this.capacity) {
                // 容量已满,删除尾部节点(最久未使用)
                const tailNode = this.tail.prev;
                this._removeNode(tailNode);
                this.cache.delete(tailNode.key);
            }
            const newNode = new Node(key, value);
            this.cache.set(key, newNode);
            this._addToHead(newNode);
        }
    }

    /**
     * 将节点从原位置移除,并添加到头部
     * @param {Node} node
     */
    _moveToHead(node) {
        this._removeNode(node);
        this._addToHead(node);
    }

    /**
     * 从链表中移除节点
     * @param {Node} node
     */
    _removeNode(node) {
        const prev = node.prev;
        const next = node.next;
        prev.next = next;
        next.prev = prev;
    }

    /**
     * 将节点插入到哨兵头节点之后(头部)
     * @param {Node} node
     */
    _addToHead(node) {
        node.prev = this.head;
        node.next = this.head.next;
        this.head.next.prev = node;
        this.head.next = node;
    }
}

/**
 * 双向链表节点
 */
class Node {
    constructor(key, value) {
        this.key = key;
        this.value = value;
        this.prev = null;
        this.next = null;
    }
}

使用示例

javascript

scss 复制代码
const lru = new LRUCache(2);
lru.put(1, 1);      // 缓存: {1=1}
lru.put(2, 2);      // 缓存: {1=1, 2=2}
console.log(lru.get(1)); // 返回 1,并移动 1 到头部 → 缓存顺序: 2,1
lru.put(3, 3);      // 容量已满,删除尾部 2 → 缓存: {1=1, 3=3}
console.log(lru.get(2)); // 返回 -1 (未找到)
lru.put(4, 4);      // 容量已满,删除尾部 1 → 缓存: {3=3, 4=4}
console.log(lru.get(1)); // 返回 -1
console.log(lru.get(3)); // 返回 3
console.log(lru.get(4)); // 返回 4

复杂度说明

  • get: 哈希表查找 O(1) + 链表移动 O(1) → 总体 O(1)
  • put: 哈希表插入/更新 O(1) + 可能删除尾部 O(1) + 链表操作 O(1) → 总体 O(1)
相关推荐
镜宇秋霖丶4 分钟前
2026.5.12@霖宇博客制作中遇见的问题
前端·vue.js·elementui
醉逍遥neo18 分钟前
mac新电脑-前端开发配置
前端·macos·ghostty
研究点啥好呢22 分钟前
DJI 机器人视觉算法工程师 面试题精选:10道高频考题+答案解析(背诵版)
算法·面试·机器人·dji
白嫖叫上我1 小时前
Vue3封装主题色完善版
前端
a1117761 小时前
细胞结构实验室(react 开源)
前端·javascript·开源·html
aaaak_1 小时前
PDD 直播间 评论 , wss hex Protobuf 解析流程分析学习
java·前端·学习
ikoala1 小时前
用了几周明基 RD280UG,我终于明白程序员为什么需要一台“专用显示器”
前端·后端·程序员
Dxy12393102161 小时前
JS如何获取元素高度
开发语言·javascript·ecmascript
文心快码BaiduComate1 小时前
Comate搭载DeepSeek-V4
前端·后端
豹哥学前端1 小时前
5分钟搞懂事件委托
前端·javascript·面试