文章目录

  • 一、题目
    • 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,则应该逐出最久未使用的关键字。
  函数 getput必须以 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、双向链表的头尾指针headtail、对于双向链表当前有多少个结点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;
}

三、本题小知识

  对于复杂的问题,要善于拆分每个子问题,对不同的子问题,采用不同的数据结构,并且进行整合梳理,最终确定采用哪种数据结构。


四、加群须知

  相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」
  那么这里,我整理了「 几十个基础算法 」 的分类,点击开启:

LeetCode 146. LRU 缓存相关推荐

  1. Leetcode 146. LRU缓存机制【哈希表 [哈希表存储每个元素在双向链表中的指针]+双向链表】

    文章目录 问题描述 解题报告 实验代码 参考资料 问题描述 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . ...

  2. Java实现 LeetCode 146 LRU缓存机制

    146. LRU缓存机制 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - ...

  3. LeetCode 146. LRU缓存机制(哈希链表)

    文章目录 1. 题目信息 2. 解题 2.1 手动实现list 2.2 使用内置list 1. 题目信息 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作 ...

  4. LeetCode —— 146. LRU缓存机制(Python)

    运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - 如果关键字 (key) 存 ...

  5. C++实现LRU算法(LeetCode 146 LRU缓存机制)

    LRU算法: LRU算法(Least Recently Used)是一种缓存淘汰策略,最近使用的数据是有用的, 如果缓存满了,删除最久没用过的数据 LRU算法描述: (1)设置缓存大小 (2)get: ...

  6. [leetcode]146. LRU缓存机制

    1.LRU(最近最少使用)缓存机制: https://baike.baidu.com/item/LRU/1269842?fr=aladdin 2.用到的数据结构: struct Value {int ...

  7. Leetcode 146. LRU缓存机制 解题思路及C++实现

    解题思路: 使用一个双向链表存储最常使用的key value对,最近使用的元素放在链表的表头,链表中最后一个元素是使用频率最低的元素.同时,使用一个map来记录对应的<key,<key, ...

  8. Leetcode 146. LRU 缓存机制

    原题链接 题解:双链表+哈希表 class LRUCache { public:struct Node {int key, val;Node *left, *right;Node(int _key, ...

  9. 【重点】LeetCode 146. LRU Cache

    LeetCode 146. LRU Cache 1.Solution1 本博客转载自:http://www.cnblogs.com/grandyang/p/4587511.html 这道题让我们实现一 ...

最新文章

  1. [二十五]JavaIO之RandomAccessFile
  2. 代码流程图怎么画_程序流图怎么画?详细图文解析绘制程序流程图
  3. [SQL] SQL 基础知识梳理(三) - 聚合和排序
  4. adf平稳性检测_ADF声明性组件示例
  5. 使用EasyPoi导出Excel
  6. 数据库新增幂等操作_使用数据库唯一键实现事务幂等性
  7. C++语言基础 —— STL —— 容器与迭代器 —— set 与 multiset
  8. BootstrapTable冻结表头(二)
  9. php数组排序语言,php数组排序函数有哪些
  10. 怎么调用html调色板,JS实现仿PS的调色板效果完整实例
  11. 微信浏览器关闭当前界面
  12. 商城 源码 java_java网上商城平台源码(含数据库脚本)
  13. java 浏览器设置字体大小_css 字体设置(不同浏览器设置效果)
  14. DeepLog:基于系统日志使用深度学习方法做异常检测和诊断
  15. hbuilder_工具的服务端口已关闭。要使用命令行调用工具,请在下方输入 y 以确认开启,或手动打开工具 -> 设置 -> 安全设置,将服务端口开启。
  16. uni-app--微信小程序自定义tabbar
  17. JavaScript运算符 ~,~~,|,,
  18. Bootstrap 4中使用BootstrapTable时需要导入popper.js
  19. DevOps 转型实践
  20. 分享:虚拟筛选常用化合物库

热门文章

  1. CSAPP第二章家庭作业参考答案
  2. 用创意和技术摘取Flash大赛桂冠
  3. Red Hat Enterprise Linux (RHEL) 8.6 发布(含下载)
  4. 基于GMap.NET地图下载器的开发和研究
  5. LinQ的初步学习与总结
  6. ANDROID基础知识普
  7. 适用于STM32的五大嵌入式操作系统,你选哪个?
  8. linux下编译安装ntfs,内核编译安装 (用NTFS模块)
  9. mysql更新等差数列求和公式_shell学习笔记(6)
  10. Linux上 journal 可以删除吗?