力扣DAY35 | 热100 | LRU缓存
前言
中等 ⚪ 这个题原本打算用双链表+最小堆做,发现无解。没想到双向链表。
题目
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity)以 正整数 作为容量capacity初始化 LRU 缓存int get(int key)如果关键字key存在于缓存中,则返回关键字的值,否则返回-1。void put(int key, int value)如果关键字key已经存在,则变更其数据值value;如果不存在,则向缓存中插入该组key-value。如果插入操作导致关键字数量超过capacity,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
示例:
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
1 <= capacity <= 30000 <= key <= 100000 <= value <= 105- 最多调用
2 * 105次get和put
思路
设计一个哈希表用key为键,val和调用次数为值。同时用最小堆维护调用次数。
我的题解
错误解法
class LRUCache {private:int cap;unordered_map<int, pair<int, int>> LRUmap;priority_queue<pair<int, int>, // 存储 {timestamp, key}vector<pair<int, int>>, // 底层容器greater<pair<int, int>>> minHeap; // 最小堆(默认是最大堆,用 greater 反转)public:LRUCache(int capacity) {cap = capacity;}int get(int key) {auto it = LRUmap.find(key);if (it != LRUmap.end()){LRUmap[key].second++;return LRUmap[key].first;}else{return -1;} }void put(int key, int value) {// 判断是否存在// 不存在 是否超出容量// 超出容量 先逐出再进入auto it = LRUmap.find(key);if (it != LRUmap.end()){LRUmap[key].first = value;}else if (LRUmap.size() >= cap){cout<<minHeap.top().second<<endl;LRUmap.erase(minHeap.top().second);minHeap.pop();LRUmap[key] = {value, 0};minHeap.push({0, key});}else{LRUmap[key] = {value, 0};minHeap.push({0, key});}}
};/*** 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);*/
官方题解
实现本题的两种操作,需要用到一个哈希表和一个双向链表。在面试中,面试官一般会期望读者能够自己实现一个简单的双向链表,而不是使用语言自带的、封装好的数据结构。在 Python 语言中,有一种结合了哈希表与双向链表的数据结构 OrderedDict,只需要短短的几行代码就可以完成本题。在 Java 语言中,同样有类似的数据结构 LinkedHashMap。
LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。
双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。
这样以来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1) 的时间内完成 get 或者 put 操作。具体的方法如下:
对于 get 操作,首先判断 key 是否存在:
如果 key 不存在,则返回 −1;
如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
对于 put 操作,首先判断 key 是否存在:
如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。
上述各项操作中,访问哈希表的时间复杂度为 O(1),在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)。而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1) 时间内完成。
小贴士
在双向链表的实现中,使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。
struct DLinkedNode {int key, value;DLinkedNode* prev;DLinkedNode* next;DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};class LRUCache {
private:unordered_map<int, DLinkedNode*> cache;DLinkedNode* head;DLinkedNode* tail;int size;int capacity;public:LRUCache(int _capacity): capacity(_capacity), size(0) {// 使用伪头部和伪尾部节点head = new DLinkedNode();tail = new DLinkedNode();head->next = tail;tail->prev = head;}int get(int key) {if (!cache.count(key)) {return -1;}// 如果 key 存在,先通过哈希表定位,再移到头部DLinkedNode* node = cache[key];moveToHead(node);return node->value;}void put(int key, int value) {if (!cache.count(key)) {// 如果 key 不存在,创建一个新的节点DLinkedNode* node = new DLinkedNode(key, value);// 添加进哈希表cache[key] = node;// 添加至双向链表的头部addToHead(node);++size;if (size > capacity) {// 如果超出容量,删除双向链表的尾部节点DLinkedNode* removed = removeTail();// 删除哈希表中对应的项cache.erase(removed->key);// 防止内存泄漏delete removed;--size;}}else {// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部DLinkedNode* node = cache[key];node->value = value;moveToHead(node);}}void addToHead(DLinkedNode* node) {node->prev = head;node->next = head->next;head->next->prev = node;head->next = node;}void removeNode(DLinkedNode* node) {node->prev->next = node->next;node->next->prev = node->prev;}void moveToHead(DLinkedNode* node) {removeNode(node);addToHead(node);}DLinkedNode* removeTail() {DLinkedNode* node = tail->prev;removeNode(node);return node;}
};
封装对象知识点
在 C++、Python 和 Go 中封装对象(面向对象编程)的方式有所不同,各有其特点和语法。以下是三种语言中对象封装的核心方法对比:
---
## **1. C++ 对象封装**
### **核心特性**
• **类(Class)** 是封装的基本单位
• 支持 **访问控制**(`public`/`private`/`protected`)
• 显式定义构造函数/析构函数
• 支持运算符重载、友元函数等高级特性
### **示例代码**
```cpp
class Person {
private:
string name; // 私有成员
int age;
public:
// 构造函数
Person(string n, int a) : name(n), age(a) {}
// 公有方法
void introduce() {
cout << "I'm " << name << ", " << age << " years old." << endl;
}
// Getter/Setter
void setName(string n) { name = n; }
string getName() { return name; }
};
// 使用
Person p("Alice", 25);
p.introduce();
```
### **关键点**
• 用 `private` 隐藏数据,`public` 暴露接口
• 手动管理内存(或使用智能指针)
---
## **2. Python 对象封装**
### **核心特性**
• 基于 **类(Class)**,但所有成员默认 **公开**
• 通过命名约定(如 `_name`)表示私有性(非强制)
• 动态特性(可运行时添加成员)
• 支持装饰器(如 `@property`)
### **示例代码**
```python
class Person:
def __init__(self, name, age):
self._name = name # 约定为"私有"(实际仍可访问)
self.age = age # 公开成员
def introduce(self):
print(f"I'm {self._name}, {self.age} years old.")
# Getter/Setter(通过@property装饰器)
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
# 使用
p = Person("Bob", 30)
p.introduce()
p.name = "Charlie" # 调用setter
```
### **关键点**
• 私有性靠约定(如 `_name`),非语言强制
• 动态绑定(可随时添加/修改成员)
---
## **3. Go 对象封装**
### **核心特性**
• 通过 **结构体(Struct)** + **方法(Method)** 实现封装
• 无类概念,但可为类型绑定方法
• 通过 **首字母大小写** 控制可见性(跨包访问)
• 大写开头:`Name`(公开)
• 小写开头:`age`(包内私有)
### **示例代码**
```go
package main
import "fmt"
type Person struct {
name string // 小写开头,包内私有
Age int // 大写开头,公开
}
// 绑定方法(值接收者)
func (p Person) Introduce() {
fmt.Printf("I'm %s, %d years old.\n", p.name, p.Age)
}
// Getter(Go习惯直接访问字段,必要时用方法)
func (p *Person) Name() string {
return p.name
}
// Setter
func (p *Person) SetName(name string) {
p.name = name
}
func main() {
p := Person{name: "Dave", Age: 35}
p.Introduce()
p.SetName("Eve")
fmt.Println("New name:", p.Name())
}
```
### **关键点**
• 封装通过 **大小写命名** 实现
• 方法绑定到类型(非类)
• 无继承,通过 **接口(Interface)** 实现多态
---
## **对比总结**
| 特性 | C++ | Python | Go |
|---------------------|--------------------------|--------------------------|-------------------------|
| **封装单位** | 类(Class) | 类(Class) | 结构体(Struct)+ 方法 |
| **访问控制** | `public`/`private` | 命名约定(如 `_name`) | 首字母大小写(`Name` vs `age`) |
| **构造/析构** | 显式定义 | `__init__` | 工厂函数(如 `NewPerson()`) |
| **动态性** | 低(需编译) | 高(运行时修改) | 低(静态类型) |
| **内存管理** | 手动/智能指针 | 垃圾回收 | 垃圾回收 |
| **多态** | 虚函数/继承 | 鸭子类型 | 接口(Interface) |
---
## **何时选择哪种语言?**
• **C++**:需要高性能、精细控制内存或硬件交互
• **Python**:快速开发、动态需求或脚本场景
• **Go**:高并发服务、简洁的语法和跨平台部署
根据项目需求选择最合适的封装方式!
心得
这个题目一开始就想错了,是逐出“最久没调用”的,而不是调用次数最小的,这是不同的概念。故不能用最小堆,只能用链表。还没仔细看怎么实现,打算明天手写一下。
重新做已ac,重点在于要想到怎么设计这个数据结构:双向链表的节点,构建链表,公共变量。get、put、链表的删除新增操作都比较好写。这里体现出架构师的重要性....不要只当把自然语言翻译成编程语言的农民工啊!!!(甚至农民工都当不上,gpt已取代)
相关文章:
力扣DAY35 | 热100 | LRU缓存
前言 中等 ⚪ 这个题原本打算用双链表最小堆做,发现无解。没想到双向链表。 题目 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int …...
Python 助力人工智能与机器学习的深度融合
技术革新的 “源动力” 在当今数字化时代,人工智能(AI)与机器学习(ML)无疑是最具影响力的技术领域,它们如同强大的引擎,推动着各个行业的变革与发展。Python 凭借其简洁易读的语法、丰富的库和…...
ARXML文件解析-1
目录 1 摘要2 ARXML文件2.1 作用及典型应用场景2.2 ARXML文件的结构树2.3 TAG(XML元素)2.4 ARXML文件关键元素解析2.4.1 XML声明与处理指令2.4.2 XML注释2.4.3 XML声明与根元素4.4.3.1 xmlns(默认命名空间)4.4.3.2 xmlns:xsi&…...
SignalR给特定User发送消息
1、背景 官网上SignalR的demo很详细,但是有个特别的问题,就是没有详细阐述如何给指定的用户发送消息。 2、解决思路 网上整体解决思路有三个: 1、最简单的方案,客户端连接SignalR的Hub时,只是简单的连接,…...
React: hook相当于函数吗?
一、Hook 是一个函数,但不仅仅是函数 函数的本质 Hook 确实是一个 JavaScript 函数,例如 useState、useEffect 或自定义 Hook 都是函数。它们可以接受参数(如初始状态值或依赖项数组),并返回结果(如状态值和…...
Ubuntu 安装 VLC
最近项目中需要用VLC查看NVR下子设备的RTSP流,特此记录,便于日后查阅。 1、安装snap $ sudo apt update $ sudo apt install snapd 2、安装vlc $ sudo snap install vlc 3、可能遇到的问题 snap beta install on ubuntu 22.04 failing to start Qt: Se…...
【数据分享】2002-2023中国湖泊水位变化数据集(免费获取)
湖泊水位变化是研究水资源动态、生态系统演变和气候变化影响的重要指标。湖泊水位的升降不仅反映了降水、蒸发和入流水量的变化,还与人类活动、气候波动及地质过程密切相关。因此,高精度、长时间序列的湖泊水位数据对于水资源管理、洪水预测以及生态环境…...
UBUNTU编译datalink
参考文档 datalink 语雀 下载 git clone https://gitee.com/liyang9512/datalink 源码打包 mvn -Prelease-datalink -Dmaven.test.skiptrue clean install -U 启动准备 # unzip ./distribution/target/datalink-server-1.0.0.tar.gz tar -xvf ./distribution/target/da…...
免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制
摘要 本文旨在探讨基于SSM框架和Android Studio的游戏搜索App的设计与实现。首先,我们详细介绍了SSM框架,这是一种经典的Java Web开发框架,由Spring、SpringMVC和MyBatis三个开源项目整合而成,为开发企业级应用提供了高效、灵活、…...
STM32单片机入门学习——第14节: [6-2] 定时器定时中断定时器外部时钟
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.04.04 STM32开发板学习——第14节: [6-2] 定时器定时中断&定时器外部时钟 前言开发…...
2025-04-03 Latex学习1——本地配置Latex + VScode环境
文章目录 1 安装 Latex2 安装 VScode3 配置环境3.1 汉化 VScode3.2 安装 latex 插件3.3 配置解释 4 编译示例5 加快你的编译5.1 取消压缩5.2 使用 PDF 代替图片 6 参考文章 1 安装 Latex 本文配置环境: Windows11 打开清华大学开源软件镜像站:https://mi…...
【CF】Day24——Codeforces Round 994 (Div. 2) D
D. Shift Esc 题目: 思路: 典DP的变种 如果这一题没有这个变换操作,那么是一个很典型的二维dp,每一个格子我们都选择上面和左边中的最小值即可 而这题由于可以变换,那我们就要考虑变换操作,首先一个显然…...
【Java集合】LinkedList源码深度分析
参考笔记:java LinkedList 源码分析(通俗易懂)_linkedlist源码分析-CSDN博客 目录 1.前言 2.LinkedList简介 3.LinkedList的底层实现 4.LinkedList 与 ArrayList 的对比 4.1 如何选择 4.2 对比图 5.LinkedList 源码Debug 5.1 add(E e) ÿ…...
第十五届蓝桥杯大赛软件赛省赛Python 大学 C 组:5.回文数组
题目1 回文数组 小蓝在无聊时随机生成了一个长度为 n 的整数数组,数组中的第 i 个数为 ai,他觉得随机生成的数组不太美观,想把它变成回文数组,也是就对于任意 i∈[1,n] 满足 a i a n − i 1 a_ia_{n−i}1 aian−i1。 小蓝…...
高并发系统架构设计的深度解析与实施指南【大模型总结】
以下是对高并发系统架构设计的深度解析与实施指南,通过技术分层拆解和场景化案例说明,呈现完整的系统设计方法论: 一、容错优先思维的系统级实现 1. 混沌工程落地框架 # 混沌实验设计模板 class ChaosExperiment:def __init__(self, scope,…...
Python办公自动化(2)对wordpdf的操作
一、操作word文档 终端下载操作word文件的工具库: pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple python-docx 1.遍历文档中内容 paragraphs:段落属性,返回列表类型的段落地址,遍历每一个段落地址,通过…...
pip安装第三方库,但PyCharm中却无法识别
点击菜单栏File,选择Settings 系统默认的是PyCharm安装目录下的python.exe 解释器,不要用。 选择你的PYTHON的安装目录下的python.exe 解释器。如果不存在的话,增加进去 如果文件》设置打不开,需移除法化包。 打开 pycharm 安装目…...
新浪财经股票每天10点自动爬取
老规矩还是先分好三步,获取数据,解析数据,存储数据 因为股票是实时的,所以要加个cookie值,最好分线程或者爬取数据时等待爬取,不然会封ip 废话不多数,直接上代码 import matplotlib import r…...
Vue2 父子组件数据传递与调用:从 ref 到 $emit
提示:https://github.com/jeecgboot/jeecgboot-vue2 文章目录 案例父组件向子组件传递数据的方式父组件调用子组件方法的方式子组件向父组件传递数据的方式流程示意图 案例 提示:以下是本篇文章正文内容,下面案例可供参考 以下是 整合后的关…...
Linux C++编译及g++使用操作
编译的步骤 编译选项参数 编译生成库文件 静态库 动态库 运行可执行文件 静态库由于已经包含了链接的文件所以可以直接执行;动态库方式由于是运行时链接,所以需要指定链接的路径;...
antvX6自定义 HTML 节点创建与更新教程
自定义 HTML 节点创建与更新教程 本文详细介绍如何利用 HTML、CSS 和 JavaScript 创建自定义节点,并通过动态更新节点数据来改变节点显示效果。无论你是否有前端基础,都能轻松跟着本教程一步步实现。 1. 基础样式设置 首先,使用 CSS 定义基…...
【Android】界面布局-线性布局LinearLayout-例子
线性布局(LinearLayout)是一种重要的界面布局中,也是经常使用到的一种界面布局 • 在线性布局中,所有的子元素都按照垂直或水平的顺序在界面上排列 ➢如果垂直排列,则每行仅包含一个界面元素 ➢如果水平排列&…...
Cortex-M 上编写汇编函数
在 ARM Cortex-M 系列单片机中使用汇编语言编写函数时,需要特别注意寄存器的使用、栈管理、调用约定以及与 C 语言的兼容性。以下是关键注意事项和示例说明: 1. 遵循 AAPCS 调用约定 ARM 定义了 AAPCS(ARM Architecture Procedure Call Standard),规定了函数调用时寄存器…...
windows技术基础知识
NT架构 NT 就是new techonology 的英文单词缩写,是微软1993年推出操作系统的重大升级,如内存管理,安全机制,多任务,多线程支持。在此之前操作系统都是基于MS-DOS上面的图形化界面,只有有限的内存管理和多任…...
在 Windows 环境下使用 VSCode 和 TinyGo 开发 ESP8266(NodeMcu) or STM32
支持的型号 https://tinygo.org/docs/reference/microcontrollers/ 1. 安装Go 2. 安装TinyGo,并添加环境变量 https://github.com/tinygo-org/tinygo/releases 3. VSCode配置,安装插件,选择设备 package mainimport ("machine"&q…...
计算机视觉算法实战——基于YOLOv8的汽车试验场积水路段识别系统
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 引言:汽车试验场智能化管理的迫切需求 在现代汽车研发流程中,试验场作为验证车辆性…...
One API:LLM API 管理 分发系统,github 24.2K Star!
随着人工智能领域的不断发展,国内外各大厂商纷纷推出了自己的 AI 大模型。面对 DeepSeek、OpenAI、Claude、腾讯元宝等众多平台的 API 接口差异,开发者常常需要花费大量时间调整代码、处理密钥管理与流量调控。One API 正是在这种背景下诞生,…...
Android Settings 有线网设置界面优化
Android Settings 有线网设置界面优化 文章目录 Android Settings 有线网设置界面优化一、前言二、简单修改1、修改的EthernetSettings代码:2、有线网ip获取代码:3、AndroidManifest.xml定义有线网的Activity4、修改后界面: 三、其他1、有线网…...
正则入门到精通
一、正则表达式入门 正则表达式本质上是一串字符序列,用于定义一个文本模式。通过这个模式,我们可以指定要匹配的文本特征。例如,如果你想匹配一个以 “abc” 开头的字符串,正则表达式可以写作 “^abc”,其中 …...
微信小程序基于Canvas实现头像图片裁剪(上)
序言 嘿,打工人混迹职场这么久,图片处理肯定都没少碰。不过咱说实话,大部分时候都是直接 “抄近道”,用现成的三方组件😏。就像我,主打一个会用工具,毕竟善用工具可是咱人类的 “超能力”&…...
