
这是一个经典的 哈希表 (Hash Map) + 双向链表 (Doubly Linked List) 的结合应用题。
为了满足 get 和 put 操作都在 O(1) 时间复杂度内完成,我们需要:
-
查找快 :使用
std::unordered_map(哈希表)来通过 Key 快速找到节点。 -
移动/插入/删除快 :使用
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)
-
为什么用
std::list?-
C++ STL 的
std::list是双向链表。 -
它提供了一个非常强大的函数
splice。这个函数可以将一个节点从链表的一个位置切下来插到另一个位置,只修改指针指向,不进行内存拷贝,时间复杂度是严格的 O(1)。 -
同时,
std::list的插入和删除(push_front,pop_back)也是 O(1)。
-
-
为什么 Map 存迭代器?
-
如果我们只存 Key,找到 Key 后还得遍历链表去移动节点,那就是 O(n) 了。
-
Map 存储了
iterator,相当于手里握着直接操作该节点的"句柄",可以直接传给splice或erase,实现 O(1) 操作。
-
-
内存管理
- 由于使用了 STL 容器,内存的申请和释放由容器自动管理,无需手动
new/delete节点,代码更安全简洁。
- 由于使用了 STL 容器,内存的申请和释放由容器自动管理,无需手动