[LeetCode146. LRU 缓存机制] | 刷题打卡

201 阅读3分钟

本文正在参与掘金团队号上线活动,点击查看大厂春招职位

题目描述

运用你所掌握的数据结构,设计和实现一个  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

难度:中等。

思路分析

  • 根据题意,有 getput 的操作,而且要求 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)