2022-07-12 11:27阅读: 127评论: 0推荐: 0

力扣-146-LRU缓存

LRU(Least Recently Used)最近最少使用,缓存这个是在《操作系统》课程上学习过的概念,会有面试要求实现也有所耳闻

需要实现的方法有3个

  1. 初始化方法,以指定的正整数作为LRU缓存结构的初始化容量

  2. get方法,如果键在缓存中,就返回键值;否则返回-1

  3. put方法,key不存在直接插入键值;存在覆盖原值
    如果插入导致超过容量,则逐出最久未使用的关键字

很明显前两个是比较容易实现的,关键就是如何记录最久未使用,以及用于实现的底层数据结构的选型

题解思路

数据结构选型

这里使用hashmap来优化双向链表查询效率低下的问题

为什么选择双向链表?因为这里涉及了很多头(插入)尾(删除)操作,如果只是单向链表的话,删除尾节点会非常麻烦,需要遍历一遍链表
为什么不用数组?数组访问快,但是插入删除就需要大量地移动元素操作,恰恰这里又有大量的插入删除操作
为什么不用栈和队列?因为栈和队列只能单位置操作(栈顶和队尾(不过说回来对头插入其实是可以的吧))

代码逻辑

  • get:如果key存在,则首先查找到这个节点(通过hash映射定位到链表位置),(维护最近使用记录)将节点移动到链表的头部,最后返回节点值

  • put:如果key不存在,则在链表头插入节点,插入完成后检查节点数量是否超过最大容量,超过则删除链表和hashmap中的对应记录
    如果key存在,则找到后覆盖key值,同时将节点移动到链表头部

实现

这里有链表的技巧是:使用额外的“伪头部”和“伪尾部”标记界限,这样就不需要在插入和删除节点时检查相邻节点是否存在

第一遍,跟着敲写注释理解

// 自己实现一个双向链表
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:
// 用map二次封装了双向链表
// 这里的key值int是什么?
unordered_map<int, DLinkedNode*> cache;
DLinkedNode* head;
DLinkedNode* tail;
int size;
// 为什么这里要有一个size变量?直接访问容器大小属性不行吗
int capacity;
public:
LRUCache(int _capacity) :capacity(_capacity), size(0) {
// 使用头尾伪节点
head = new DLinkedNode();
tail = new DLinkedNode();
head->next = tail;
tail->prev = head;
}
// 辅助方法,前两个一增一删又是为第三个移动到头部服务
void addToHead(DLinkedNode* node) {
// 先插入node再改head
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);
}
DLinkedNode* removeTail() {
// 将伪伪节点的前一个真伪节点删除,并返回被删除掉的元素
DLinkedNode* node = tail->prev;
removeNode(node);
return node;
}
int get(int key) {
if (!cache.count(key)) {
// 如果在map中对key技术不为0
// 说明key存在,返回-1
return -1;
}
// 如果存在,则先将节点移动到头部,再返回键值
// 将找到的节点复制一份,浅拷贝仅复制了引用
// 这里为什么要以这种方式创建一个节点?其实不用这个局部变量也可以吧
// hashmap可以用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);
// 向hash表添加还能这么添加吗
cache[key] = node;
++size;
if (size > capacity) {
// 如果新增节点后大小大于容量,就把链表中的最后一个删除
DLinkedNode* removed = removeTail();
// 删除哈希表中的元素
cache.erase(removed->key);// 链表中其实没有真的删除,只是失去了链接关系,那么垃圾回收是怎么执行的
// new完delete,防止内存泄露
delete removed;
--size;
}
}
else {
// 如果key存在
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node);
}
}
};

第二遍,边瞄边写

上面第一遍的代码有些问题,主要是漏了两句语句

以下是第二遍的代码

// 自行实现一个双向链表
struct DLinkedList{
int key;
int value;
DLinkedList* prev;
DLinkedList* next;
// 这里结构体居然有构造函数,真是跟class越来越像了
// 下面分别是有参和无参的构造函数,而且是用的新语法
DLinkedList():key(0),value(0),prev(nullptr),next(nullptr){}
DLinkedList(int _key,int _value):key(_key),value(_value),prev(nullptr),next(nullptr){}
};
class LRUCache {
// 本题中使用hashmap封装双向链表实现底层数据结构
// 最近最少使用,链表中的头表示最近使用的节点数据,顺序表示使用时间的远近
// 而hashmap则是为了加快链表的检索速度
private:
unordered_map<int,DLinkedList*> map;
DLinkedList* head;
DLinkedList* tail;
int size;
int capacity;
public:
LRUCache(int _capacity):size(0),capacity(_capacity) {
// 这里要对两个节点初始化
head = new DLinkedList();
tail = new DLinkedList();
head->next=tail;
tail->prev=head;
}
// 三个辅助方法
void addToHead(DLinkedList* node){
// size++要不早写在这里?不要
node->prev = head;
node->next = head->next;
head->next->prev=node;
head->next=node;
}
void removeNode(DLinkedList* node){
//
node->prev->next=node->next;
node->next->prev=node->prev;
}
void moveToHead(DLinkedList* node){
//
removeNode(node);
addToHead(node);
}
DLinkedList* removeTail(){
// 这里使用一个局部变量主要是为了删除后返回,不然删了之后就没有指针指向,就找不到了
DLinkedList* node = tail->prev;
removeNode(node);
return node;
}
int get(int key) {
// 如果key不存在,返回-1
if(!map.count(key)){
return -1;
}else{
DLinkedList* node = map[key];
moveToHead(node);
return node->value;
}
// 如果key存在,返回key值并将节点移动到链表头
}
void put(int key, int value) {
// 如果key不存在,则插入节点到链表头
if(!map.count(key)){
DLinkedList* node = new DLinkedList(key,value);
// 这里先插入到了哈希表而不是链表,中,如果有原值则会被覆盖
map[key]=node;
addToHead(node);
++size;
if(size>capacity){
DLinkedList* removed = removeTail();
map.erase(removed->key);
delete removed;
--size;
}
}else{
// 如果key存在就覆盖value,并且把节点移动到链表头
DLinkedList* temp = map[key];
temp->value = value;
moveToHead(temp);
}
}
};

感觉这题本质上更像是“实现某种数据结构”的题型

本文作者:YaosGHC

本文链接:https://www.cnblogs.com/yaocy/p/16469430.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   YaosGHC  阅读(127)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起