LeetCode hot 力扣100 LRU 缓存

devtools/2025/1/21 16:48:12/

splice 是 C++ STL 的 std::list 提供的一个成员函数,用于高效地将一个或多个元素从一个链表移动到另一个链表,或在同一链表内重新排列元素。它不涉及数据的拷贝,而是直接修改链表节点的指针,因此操作非常高效。splice 的功能splice 可以在以下场景中使用:1.	从同一个链表中移动元素到目标位置。2.	从另一个链表中移动元素到当前链表。函数签名splice 有以下几种重载形式:1.	移动整个链表:void splice(iterator pos, list& other);•	pos: 要插入的位置。•	other: 将整个链表 other 插入到当前链表的 pos 位置。2.	移动单个元素:void splice(iterator pos, list& other, iterator it);•	pos: 要插入的位置。•	other: 元素来自的链表。•	it: 要移动的元素的迭代器。3.	移动多个元素(范围):void splice(iterator pos, list& other, iterator first, iterator last);•	pos: 要插入的位置。•	other: 元素来自的链表。•	first 和 last: 范围 [first, last) 中的元素将被移动。你的代码中的 splice在以下代码中:v.splice(v.begin(), v, idx[key]);含义:1.	参数含义:•	v.begin(): 当前链表 v 的起始位置(头部)。•	v: 当前链表本身。•	idx[key]: 指向 key 对应的链表节点的迭代器。2.	操作:
将链表中 key 对应的节点从当前位置移动到链表头部。为什么使用 splice:•	这是一个 O(1) 的操作,因为 splice 不会创建或销毁节点,只会修改链表中节点的指针,直接完成移动。•	这使得 LRU 缓存能够高效地调整最近使用元素的位置,而不需要拷贝或重建节点。示例代码移动单个元素:#include <iostream>
#include <list>
using namespace std;int main() {list<int> l = {1, 2, 3, 4, 5};auto it = next(l.begin(), 2);  // 指向第三个元素(值为 3)// 将第三个元素移动到链表的头部l.splice(l.begin(), l, it);for (int x : l) {cout << x << " ";  // 输出:3 1 2 4 5}return 0;
}移动范围:#include <iostream>
#include <list>
using namespace std;int main() {list<int> l1 = {1, 2, 3, 4, 5};list<int> l2 = {10, 20, 30};// 将 l1 中第 2 到第 4 个元素([2, 3, 4])移动到 l2 的尾部l2.splice(l2.end(), l1, next(l1.begin(), 1), next(l1.begin(), 4));for (int x : l2) {cout << x << " ";  // 输出:10 20 30 2 3 4}return 0;
}总结•	splice 是 std::list 专有的成员函数,用于高效地移动链表节点。•	它适用于重排链表元素或在不同链表之间移动数据。•	在你的 LRU 缓存代码中,splice 将最近访问的元素(key)移动到链表头部,表示其是“最新使用”的。
class LRUCache {// 用于存储缓存数据的双向链表,存储键值对 (key, value)list<pair<int, int>> v;// 用于存储 key 对应在链表中的迭代器的哈希表unordered_map<int, list<pair<int, int>>::iterator> idx;// 缓存的最大容量int capacity;public:// 构造函数,初始化缓存容量LRUCache(int capacity) : capacity(capacity) {}// 获取 key 对应的值int get(int key) {// 如果 key 不存在,返回 -1if (idx.count(key) == 0) return -1;// 将 key 对应的元素移动到链表的头部(表示最近使用)v.splice(v.begin(), v, idx[key]);// 返回 key 对应的值(位于链表头部)return v.front().second;}// 插入或更新 key-value 对void put(int key, int value) {// 如果 key 已经存在if (idx.count(key)) {// 将 key 对应的元素移动到链表头部v.splice(v.begin(), v, idx[key]);// 更新链表头部的值v.front().second = value;} else {// 如果 key 不存在,直接在链表头部插入新的 key-value 对v.emplace_front(key, value);// 将 key 与链表头部的迭代器关联idx[key] = v.begin();}// 如果链表大小超过容量,移除最久未使用的元素(链表尾部)if (v.size() > capacity) {// 从哈希表中移除对应的 keyidx.erase(v.back().first);// 从链表中移除尾部元素v.pop_back();}}
};

以下是注释版代码以及每行的解释:

代码解析

1. 成员变量:

• list<pair<int, int>> v:

• 一个双向链表,用来存储缓存中的数据对 (key, value)。

• 双向链表允许高效地在任意位置插入或删除元素。

• unordered_map<int, list<pair<int, int>>::iterator> idx:

• 用哈希表存储每个 key 在链表中的位置(迭代器)。

• 通过哈希表,get 和 put 操作可以快速定位链表中的元素。

• int capacity:

• 缓存的最大容量。

2. 构造函数:

• 初始化缓存的容量 capacity。

3. get 方法:

• 检查 key 是否存在于缓存中:

• 如果不存在,返回 -1。

• 如果存在,将对应的元素移动到链表头部,表示最近使用:

• 使用 v.splice 方法实现,时间复杂度为 。

• 返回该元素的值。

4. put 方法:

• 如果 key 已存在:

• 通过 idx[key] 快速定位到链表中的元素,将其移动到链表头部。

• 更新链表头部的值。

• 如果 key 不存在:

• 在链表头部插入新的 (key, value) 对。

• 将 key 和链表头部的迭代器存入 idx。

• 如果链表大小超过了缓存容量:

• 移除链表尾部的元素(最久未使用的元素)。

• 从 idx 中删除对应的 key。

5. 时间复杂度:

• get 操作:,因为哈希表定位和链表操作都是常数时间。

• put 操作:,同理。

6. 核心思想:

• 使用链表维护元素的使用顺序,头部是最近使用的,尾部是最久未使用的。

• 使用哈希表快速定位 key 在链表中的位置,从而高效实现 get 和 put 操作。

这段代码实现了一个 LRU (Least Recently Used) Cache,并且给出了一个输入序列和期望的输出序列。以下是整个程序的执行过程以及输出结果的计算逻辑。

输入

• 操作序列:

["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]

• 对应参数:

[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]

• 缓存容量为 2。

输出

• 预期结果:

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

执行过程分析

操作 1:LRUCache(2)

• 初始化一个容量为 2 的 LRU 缓存。

• 输出:null(构造函数无返回值)。

操作 2:put(1, 1)

• 插入键值对 (1, 1)。

• 缓存状态:[(1, 1)](最近使用的在前,容量未满)。

• 输出:null。

操作 3:put(2, 2)

• 插入键值对 (2, 2)。

• 缓存状态:[(2, 2), (1, 1)](最近使用的在前,容量已满)。

• 输出:null。

操作 4:get(1)

• 访问键 1,值为 1。

• 将键 1 移动到链表头部,表示它是最近使用的。

• 缓存状态:[(1, 1), (2, 2)]。

• 输出:1。

操作 5:put(3, 3)

• 插入键值对 (3, 3)。

• 缓存已满,移除最久未使用的键 (2, 2)。

• 插入键 (3, 3) 到链表头部。

• 缓存状态:[(3, 3), (1, 1)]。

• 输出:null。

操作 6:get(2)

• 访问键 2,发现它不在缓存中。

• 输出:-1。

操作 7:put(4, 4)

• 插入键值对 (4, 4)。

• 缓存已满,移除最久未使用的键 (1, 1)。

• 插入键 (4, 4) 到链表头部。

• 缓存状态:[(4, 4), (3, 3)]。

• 输出:null.

操作 8:get(1)

• 访问键 1,发现它不在缓存中。

• 输出:-1。

操作 9:get(3)

• 访问键 3,值为 3。

• 将键 3 移动到链表头部,表示它是最近使用的。

• 缓存状态:[(3, 3), (4, 4)]。

• 输出:3。

操作 10:get(4)

• 访问键 4,值为 4。

• 将键 4 移动到链表头部,表示它是最近使用的。

• 缓存状态:[(4, 4), (3, 3)]。

• 输出:4。

总结

最终输出结果为:

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

每一步操作严格按照 LRU 缓存的定义执行,符合期望结果。


http://www.ppmy.cn/devtools/152380.html

相关文章

windows下使用docker执行器并配置 hosts 解析

本篇目录 1. 问题背景2. 环境准备2.1 云上开通windows 2022 英文版机器2.1.1 安装 git2.1.2 安装 runner2.1.3 装docker2.1.4 注册runner并使用docker执行器 3. 项目信息3.1 编写window bat脚本3.2 项目.gitlab-ci.yml文件 4. 测试结论4.1 运行流水线 5. troubleshooting问题1&…

Vue2:el-tree用scope slot为每一个节点添加一个鼠标悬浮时出现的右对齐的按钮

el-tree中,每一个节点后面添加一个按钮,响应除节点点击事件之外的操作,要求: 1、按钮在鼠标悬浮在该节点之上时才出现 2、按钮右对齐 实现如下。 1、为每个节点添加按钮 从官网说明来看,有两种方式添加按钮,render-content和 scoped slot,我使用的是scoped slot方式…

小红书用户作品列表 API 系列,返回值说明

item_search_shop_video-获得某书用户作品列表 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_sea…

VB.net实战(VSTO):解决WPS Ribbon图标灰色背景

问题&#xff1a;用VSTO制作插件&#xff0c;在MS Office中图标显示正常&#xff0c;但在WPS Office中图标显示为灰色背景 原因&#xff1a;使用的图标是纯透明背景的&#xff0c;这样的图标在WPS中会变为灰色背景。 以下这个解决办法是我自己摸索出来的&#xff0c;对您有用的…

你了解什么是股指期货贴水套利吗?

首先&#xff0c;咱们来聊聊什么是股指期货。股指期货&#xff0c;简单来说&#xff0c;就是一种以股价指数为“标的物”的期货合约。就像咱们平时买卖的商品期货&#xff0c;比如大豆、原油那些&#xff0c;只不过这里交易的“商品”是股价指数&#xff0c;是一种标准化的金融…

我的常用vim操作

(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu) 我的常用vi操作 1. 文件打开与保存 打开文件操作&#xff1a;vi xxx.h 查看文件&#xff0c;不修改&#xff0c;&#xff1a;view xxx.h 写入并保存&#xff1a;:wq 或 :x 有修改强制退出&#xff0c;不保存&#x…

Golang学习笔记_27——单例模式

Golang学习笔记_24——泛型 Golang学习笔记_25——协程Golang学习笔记_25——协程 Golang学习笔记_26——通道 文章目录 单例模式1. 介绍2. 应用场景3. 实现3.1 饿汉式3.2 懒汉模式 源码 单例模式 1. 介绍 单例模式是一种创建型设计模式&#xff0c;它确保一个类只有一个实例…

C语言:-三子棋游戏代码:分支-循环-数组-函数集合

思路分析&#xff1a; 1、写菜单 2、菜单之后进入游戏的操作 3、写函数 实现游戏 3.1、初始化棋盘函数&#xff0c;使数组元素都为空格 3.2、打印棋盘 棋盘的大概样子 3.3、玩家出棋 3.3.1、限制玩家要下的坐标位置 3.3.2、判断玩家要下的位置是否由棋子 3.4、电脑出棋 3.4.1、…