LeetCode 146. LRU 缓存
文章目录
- 一、题目
- 1、题目描述
- 2、基础框架
- 3、原题链接
- 二、解题报告
- 1、思路分析
- 2、时间复杂度
- 3、代码详解
- 1)定义
- 2)初始化
- 3)值的插入
- 4)值的获取
- 三、本题小知识
- 四、加群须知
一、题目
1、题目描述
请你设计并实现一个满足
LRU
(最近最少使用) 缓存 约束的数据结构。实现LRUCache
类:
LRUCache(int capacity)
以 正整数作为容量capacity
初始化LRU
缓存;
int get(int key)
如果关键字 keykeykey 存在于缓存中,则返回关键字的值,否则返回-1
。
void put(int key, int value)
如果关键字key
已经存在,则变更其数据值value
;如果不存在,则向缓存中插入该组key-value
。如果插入操作导致关键字数量超过capacity
,则应该逐出最久未使用的关键字。
函数get
和put
必须以 O(1)O(1)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]
2、基础框架
- C语言 版本给出的基础框架代码如下:
typedef struct {} LRUCache;LRUCache* lRUCacheCreate(int capacity) {}int lRUCacheGet(LRUCache* obj, int key) {}void lRUCachePut(LRUCache* obj, int key, int value) {}void lRUCacheFree(LRUCache* obj) {}
3、原题链接
LeetCode 146. LRU 缓存
面试题 16.25. LRU 缓存
剑指 Offer II 031. 最近最少使用缓存
二、解题报告
1、思路分析
(1)(1)(1) 对于这种设计题,我们首先要想清楚每个步骤的时间复杂度要求,如果数据给定的操作是常数级别,那么这个操作可以采用 O(n)O(n)O(n) 的算法;否则就要往 O(1)O(1)O(1) 或者 O(logn)O(logn)O(logn) 去考虑。
(2)(2)(2) 对于创建操作lRUCacheCreate
,只有一次操作,所以 O(n)O(n)O(n) 一般是没有什么问题的。
(3)(3)(3) 对于获取操作lRUCacheGet
,如果要求 O(1)O(1)O(1),只有 数组 或者 哈希表 (大概率就是哈希表了)。
(4)(4)(4) 对于插入操作lRUCachePut
,如果要求 O(1)O(1)O(1),数组放进最后一个元素的操作是 O(1)O(1)O(1) 的,链表放进第一个元素的操作是 O(1)O(1)O(1) 的。
(5)(5)(5) 如果插入操作导致关键字数量超过 capacity
,则应该逐出最久未使用的关键字。表明 插入 和 删除 操作是操作的一个表的头和尾。要求头和尾都能够进行插入删除的,是队列。但是,在执行插入操作的时候,如果一个数被插入,势必会改变它的 “最久未使用” 属性,想要快速改变位置,就只能用链表,然后又涉及到头和尾,所以必须是 双向链表 (所以,它不是一个队列)。
(6)(6)(6) 所以这个题,需要用到的数据结构就是 双向链表 + 哈希表。
2、时间复杂度
O(n)O(n)O(n)。
3、代码详解
1)定义
定义一个双向链表的结点BiNode
,包含值val
、前驱结点prev
、后继结点next
。
再定义一个LRUCache
,包含双向链表的结点哈希表hash
、双向链表的头尾指针head
和tail
、对于双向链表当前有多少个结点size
、双向链表总共能够存放多少个结点capacity
。
struct BiNode {int key;int val;struct BiNode *prev;struct BiNode *next;
};typedef struct {struct BiNode *hash[100001];struct BiNode *head;struct BiNode *tail;int size;int capacity;
} LRUCache;
2)初始化
初始化就是把定义的结构赋初值。
obj->capacity
表示这个LRU
缓存的最大结点个数;
obj->size
表示这个LRU
缓存的当前结点个数;
obj->head
表示这个LRU
缓存的双向链表的头结点;
obj->tail
表示这个LRU
缓存的双向链表的尾结点;
obj->hash
表示这个LRU
缓存的值到双向链表的结点的映射;
LRUCache* lRUCacheCreate(int capacity) {LRUCache* obj = (LRUCache *)malloc( sizeof(LRUCache) );obj->capacity = capacity;obj->size = 0;obj->head = obj->tail = NULL;memset(obj->hash, NULL, sizeof(obj->hash));return obj;
}
3)值的插入
void put(int key, int value)
如果关键字 key
已经存在,则变更其数据值 value
;如果不存在,则向缓存中插入该组 key-value
。如果插入操作导致关键字数量超过 capacity
,则应该 逐出 最久未使用的关键字。
我们需要抽象出双向链表的插入操作、删除操作。先来看插入操作。
对于我们需要实现的双向链表来说,插入操作一定是在头结点插入的,因为在执行插入的当下,它一定是一个 “最近使用” 的结点。它的优先级是最高的。
void BiNodeAdd(struct BiNode **head, struct BiNode **tail, struct BiNode *node) {if(*head == NULL) {*head = *tail = node;}else {node->prev = NULL;node->next = *head;(*head)->prev = node;*head = node;}
}
对于我们需要实现的双向链表来说,删除操作可以是任意结点,因为不一定是被元素个数达到上限以后淘汰出去的,也有可能是之前存在结点,调整了位置。那么对于双向链表的删除操作,我们需要考虑目前有 0个结点、1个结点、以及被删除的结点是头结点、是尾结点、以及中间结点。
void BiNodeDel(struct BiNode **head, struct BiNode **tail, struct BiNode *node) {if(*head == NULL) {return ;}else if( *head == *tail ) {*head = *tail = NULL;}else {if(*head == node) {*head = node->next;(*head)->prev = NULL;}else if(*tail == node) {*tail = node->prev;(*tail)->next = NULL;}else {node->prev->next = node->next;node->next->prev = node->prev;}}node->prev = node->next = NULL;
}
如何把值塞入这个LRU
缓存中呢?如果从哈希表里面找不到这个结点,则执行双向链表的插入操作、更新哈希表、更新size
;如果能够找到,则更新val
、删除原结点、插入新结点。
最后,需要判断链表的长度是否超过了capacity
,如果超过了,移除双向链表的尾结点,并且修改size
属性、清理内存。
void lRUCachePut(LRUCache* obj, int key, int value) {struct BiNode *bnode = obj->hash[key];if(bnode == NULL) {bnode = (struct BiNode *) malloc( sizeof(struct BiNode) );bnode->key = key;bnode->val = value;bnode->prev = bnode->next = NULL;BiNodeAdd(&obj->head, &obj->tail, bnode);obj->hash[key] = bnode;obj->size ++;}else {bnode->val = value;BiNodeDel(&obj->head, &obj->tail, bnode);BiNodeAdd(&obj->head, &obj->tail, bnode);}if(obj->size > obj->capacity) {bnode = obj->tail;BiNodeDel(&obj->head, &obj->tail, bnode);obj->hash[bnode->key] = NULL;obj->size --;free(bnode);}
}
4)值的获取
int get(int key)
如果关键字 key
存在于缓存中,则返回关键字的值,否则返回 -1
。首先,通过哈希表进行查找,如果哈希表里面没有元素,直接返回 -1
即可,如果有元素,我们需要执行一次put
操作。
为什么要执行一次put
操作?原因是因为它改变了 “最近最久未使用” 这个属性,所以相当于它变成了 “最近使用” 的元素。
int lRUCacheGet(LRUCache* obj, int key) {struct BiNode *bn = obj->hash[key];if(bn == NULL) {return -1;}lRUCachePut(obj, bn->key, bn->val);return bn->val;
}
三、本题小知识
对于复杂的问题,要善于拆分每个子问题,对不同的子问题,采用不同的数据结构,并且进行整合梳理,最终确定采用哪种数据结构。
四、加群须知
相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」。
那么这里,我整理了「 几十个基础算法 」 的分类,点击开启:
文章目录 问题描述 解题报告 实验代码 参考资料 问题描述 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . ... 146. LRU缓存机制 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - ... 文章目录 1. 题目信息 2. 解题 2.1 手动实现list 2.2 使用内置list 1. 题目信息 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作 ... 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - 如果关键字 (key) 存 ... LRU算法: LRU算法(Least Recently Used)是一种缓存淘汰策略,最近使用的数据是有用的, 如果缓存满了,删除最久没用过的数据 LRU算法描述: (1)设置缓存大小 (2)get: ... 1.LRU(最近最少使用)缓存机制: https://baike.baidu.com/item/LRU/1269842?fr=aladdin 2.用到的数据结构: struct Value {int ... 解题思路: 使用一个双向链表存储最常使用的key value对,最近使用的元素放在链表的表头,链表中最后一个元素是使用频率最低的元素.同时,使用一个map来记录对应的<key,<key, ... 原题链接 题解:双链表+哈希表 class LRUCache { public:struct Node {int key, val;Node *left, *right;Node(int _key, ... LeetCode 146. LRU Cache 1.Solution1 本博客转载自:http://www.cnblogs.com/grandyang/p/4587511.html 这道题让我们实现一 ...LeetCode 146. LRU 缓存相关推荐
最新文章
热门文章