本文正在参与掘金团队号上线活动,点击查看大厂春招职位
题目描述
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
- LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
- int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
- void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 进阶:你是否可以在 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 <= 3000
0 <= key <= 3000
0 <= value <= 104
最多调用 3 * 104 次 get 和 put
难度:中等。
思路分析
- 根据题意,有
get和put的操作,而且要求O(1)复杂度,想到用哈希表 - 需要对结构体进行频繁的写入、删除,那可以使用链表来实现
- 结合起来,整个解题的思路是用哈希表 + 双链表来实现整个结构
- 双链表用来存储缓存,需要实现插入的逻辑
- 哈希表主要用来记录,通过
key生成键,方便快速确定缓存是否重叠、寻找值的位置 - 哈希表的值对应着双链表的节点
AC 代码
#define Nothingness -1
// 创建双链表结构
struct node {
int key;
int value;
struct node* prev;
struct node* next;
};
// 双链表插入结点的方法
void InsertNode(struct node* head, struct node* cur) {
if(cur->prev == NULL && cur->next == NULL) {
// 新结点,增加
cur->prev = head;
cur->next = head->next;
head->next->prev = cur;
head->next = cur;
}else {
struct node* first = head->next;
if(first != cur) {
// 旧结点,修改
cur->prev->next = cur->next;
cur->next->prev = cur->prev;
cur->prev = head;
cur->next = head->next;
head->next->prev = cur;
head->next = cur;
}
}
}
// 创建哈希结构
struct hash {
struct node* unused;
struct hash* next;
};
// 寻找哈希地址
struct hash* HashMap(struct hash* table, int key, int capacity) {
int addr = key % capacity;
return &table[addr];
}
// 缓存结构
typedef struct {
int size;
int capacity;
struct hash* table;
struct node* head;
struct node* tail;
} LRUCache;
// 新建缓存结构
LRUCache* lRUCacheCreate(int capacity) {
LRUCache* obj = (LRUCache*)malloc(sizeof(LRUCache));
obj->table = (struct hash*)malloc(capacity * sizeof(struct hash));
memset(obj->table, 0, capacity * sizeof(struct hash));
obj->head = (struct node*)malloc(sizeof(struct node));
obj->tail = (struct node*)malloc(sizeof(struct node));
obj->head->prev = NULL;
obj->head->next = obj->tail;
obj->tail->prev = obj->head;
obj->tail->next = NULL;
obj->size = 0;
obj->capacity = capacity;
return obj;
}
// 获取缓存中某个值
int lRUCacheGet(LRUCache* obj, int key) {
struct hash* addr = HashMap(obj->table, key, obj->capacity);
addr = addr->next;
if(addr == NULL) return Nothingness;
while(addr->next != NULL && addr->unused->key != key) {
addr = addr->next;
}
if(addr->unused->key == key) {
// 双链表提到最前面,返回值
InsertNode(obj->head, addr->unused);
return addr->unused->value;
}
return Nothingness;
}
// 在缓存中存储某个值
void lRUCachePut(LRUCache* obj, int key, int value) {
// 首先判断是否已存在,其次判断容量是否足够
// 不存在时哈希和链表都要操作,存在时只操作链表
struct hash* addr = HashMap(obj->table, key, obj->capacity);
if(lRUCacheGet(obj, key) == Nothingness) {
// 不存在 判断容量是否足够
if(obj->size >= obj->capacity) {
// 首先删除没用的结点
struct node* last = obj->tail->prev;
struct hash* remove = HashMap(obj->table, last->key, obj->capacity);
struct hash* ptr = remove;
remove = remove->next;
while(remove->unused->key != last->key) {
ptr = remove;
remove = remove->next;
}
ptr->next = remove->next;
remove->next = NULL;
remove->unused = NULL;
free(remove);
// 把last利用起来
last->key = key;
last->value = value;
InsertNode(obj->head, last);
// 更新哈希
struct hash* new_code = (struct hash*)malloc(sizeof(struct hash));
new_code->next = addr->next;
addr->next = new_code;
new_code->unused = last;
}else {
// 容量够,直接插入
struct hash* new_code = (struct hash*)malloc(sizeof(struct hash));
new_code->unused = (struct node*)malloc(sizeof(struct node));
new_code->next = addr->next;
addr->next = new_code;
new_code->unused->key = key;
new_code->unused->value = value;
new_code->unused->prev = NULL;
new_code->unused->next = NULL;
InsertNode(obj->head, new_code->unused);
++(obj->size);
}
}else {
// 已存在,操作链表,由于执行了get,插入结点已经在链表最前端
obj->head->next->value = value;
}
}
void lRUCacheFree(LRUCache* obj) {
free(obj->table);
free(obj->head);
free(obj->tail);
free(obj);
}
复杂度分析
- 时间复杂度:
O(1) - 空间复杂度:
O(n)