链表-力扣hot100-LRU缓存-146

这是一个经典的 哈希表 (Hash Map) + 双向链表 (Doubly Linked List) 的结合应用题。

为了满足 getput 操作都在 O(1) 时间复杂度内完成,我们需要:

  1. 查找快 :使用 std::unordered_map(哈希表)来通过 Key 快速找到节点。

  2. 移动/插入/删除快 :使用 std::list(双向链表)来维护数据的使用顺序。

    • 链表头 (Front) :存放最近最少使用 (MRU) 的数据。

    • 链表尾 (Back) :存放最久未被使用 (LRU) 的数据,当缓存满时,优先剔除这里的数据。

核心逻辑设计

  • Cache List (std::list) :存储实际的 <Key, Value> 对。为什么还要存 Key?因为当我们要从链表尾部删除节点时,我们需要通过这个 Key 去哈希表里把对应的映射也删掉。

  • Cache Map (std::unordered_map) :存储 Key -> std::list::iterator(指向链表中对应节点的迭代器)。这样我们就能通过 Key 瞬间定位到链表中的具体位置。

完整代码实现

C++

复制代码
#include <unordered_map>
#include <list>
#include <utility> // for std::pair

class LRUCache {
private:
    int capacity;
    // 双向链表:存储 key 和 value。
    // 约定:链表头部(begin)是最近使用的,尾部(end)是最久未使用的。
    std::list<std::pair<int, int>> cacheList; 
    
    // 哈希表:存储 key 到 链表节点迭代器 的映射,实现 O(1) 定位
    std::unordered_map<int, std::list<std::pair<int, int>>::iterator> cacheMap;

public:
    LRUCache(int capacity) : capacity(capacity) {
        
    }
    
    int get(int key) {
        // 1. 查找 key 是否存在
        auto it = cacheMap.find(key);
        if (it == cacheMap.end()) {
            return -1; // 不存在
        }

        // 2. 如果存在,需要将该节点移动到链表头部(标记为最近使用)
        // list::splice 函数可以在 O(1) 时间内将节点从一个位置转移到另一个位置
        // 参数说明:目标位置(头部), 源链表, 源节点迭代器
        cacheList.splice(cacheList.begin(), cacheList, it->second);

        // 3. 返回 value
        return it->second->second;
    }
    
    void put(int key, int value) {
        auto it = cacheMap.find(key);
        
        // 情况 1: Key 已经存在 -> 更新 value 并移动到头部
        if (it != cacheMap.end()) {
            it->second->second = value; // 更新值
            cacheList.splice(cacheList.begin(), cacheList, it->second); // 移动到头部
            return;
        }

        // 情况 2: Key 不存在 -> 插入新节点
        // 先判断容量是否已满
        if (cacheList.size() == capacity) {
            // 容量已满,淘汰链表尾部(最久未使用)的节点
            int key_to_remove = cacheList.back().first; // 获取尾部 key
            cacheList.pop_back();                       // 从链表移除
            cacheMap.erase(key_to_remove);              // 从哈希表移除
        }

        // 插入新节点到头部
        cacheList.push_front({key, value});
        // 在哈希表中记录 key 和对应的迭代器(指向头部)
        cacheMap[key] = cacheList.begin();
    }
};

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

关键点解析 (Why it works)

  1. 为什么用 std::list?

    • C++ STL 的 std::list 是双向链表。

    • 它提供了一个非常强大的函数 splice 。这个函数可以将一个节点从链表的一个位置切下来插到另一个位置,只修改指针指向,不进行内存拷贝,时间复杂度是严格的 O(1)

    • 同时,std::list 的插入和删除(push_front, pop_back)也是 O(1)

  2. 为什么 Map 存迭代器?

    • 如果我们只存 Key,找到 Key 后还得遍历链表去移动节点,那就是 O(n) 了。

    • Map 存储了 iterator,相当于手里握着直接操作该节点的"句柄",可以直接传给 spliceerase,实现 O(1) 操作。

  3. 内存管理

    • 由于使用了 STL 容器,内存的申请和释放由容器自动管理,无需手动 new/delete 节点,代码更安全简洁。
相关推荐
黎梨梨梨_2 小时前
双向链表的实现
数据结构·链表
咫尺的梦想00713 小时前
链表——删除链表的倒数第 N 个结点
数据结构·链表
咫尺的梦想00715 小时前
链表-反装链表
数据结构·链表
蘑菇小白1 天前
数据结构--链表
数据结构·链表
FuckPatience2 天前
C# 实现元素索引由1开始的链表
开发语言·链表·c#
无敌最俊朗@2 天前
链表-力扣hot100-随机链表的复制138
数据结构·leetcode·链表
leoufung3 天前
链表题目讲解 —— 删除链表的倒数第 n 个节点(LeetCode 19)
数据结构·leetcode·链表
JHC0000003 天前
交换链表中的节点
数据结构·链表
做怪小疯子4 天前
LeetCode 热题 100——链表——反转链表
算法·leetcode·链表