牛客题解 | 设计LRU缓存结构

news/2025/2/21 10:08:21/文章来源:https://www.cnblogs.com/wc529065/p/18728692

题目

题目链接

题目的主要信息:
  • 实现LRU缓存的模拟结构,包括加入函数set,访问函数get
  • 结构有长度限制,加入新数时,超出长度则需要删除最不常访问的,其中set与get都访问
  • 两个函数都是\(O(1)\)
举一反三:

学习完本题的思路你可以解决如下题目:

BM101. 设计LFU缓存结构

方法:哈希表+双向链表(推荐使用)

知识点1:哈希表

哈希表是一种根据关键码(key)直接访问值(value)的一种数据结构。而这种直接访问意味着只要知道key就能在\(O(1)\)时间内得到value,因此哈希表常用来统计频率、快速检验某个元素是否出现过等。

知识点2:双向链表

双向链表是一种特殊的链表,它除了链表具有的每个节点指向后一个节点的指针外,还拥有一个每个节点指向前一个节点的指针,因此它可以任意向前或者向后访问,每次更改节点连接状态的时候,需要变动两个指针。

思路:

插入与访问值都是\(O(1)\),没有任何一种数据结构可以直接做到。

于是我们可以想到数据结构的组合:访问\(O(1)\)很容易想到了哈希表;插入\(O(1)\)的数据结构有很多,但是如果访问到了这个地方再选择插入,且超出长度要在\(O(1)\)之内删除,我们可以想到用链表,可以用哈希表的key值对应链表的节点,完成直接访问。但是我们还需要把每次访问的key值节点加入链表头,同时删掉链表尾,所以选择双向链表,便于删除与移动。

于是我们的方法就是哈希表+双向链表。

具体做法:

  • step 1:构建一个双向链表的类,记录key值与val值,同时一前一后两个指针。用哈希表存储key值和链表节点,这样我们可以根据key值在哈希表中直接锁定链表节点,从而实现在链表中直接访问,能够做到\(O(1)\)时间访问链表任意节点。
    //设置双向链表结构class Node{ int key;int val;Node pre;Node next;//初始化public Node(int key, int val) {this.key = key;this.val = val;this.pre = null;this.next = null;}}
  • step 2:设置全局变量,记录双向链表的头、尾及LRU剩余的大小,并全部初始化,首尾相互连接好。
//构建初始化连接
//链表剩余大小
this.size = k;
this.head.next = this.tail;
this.tail.pre = this.head;
  • step 3:遍历函数的操作数组,检查第一个元素判断是属于set操作还是get操作。
  • step 4:如果是set操作,即将key值与val值加入链表,我们先检查链表中是否有这个key值,可以通过哈希表检查出,如果有直接通过哈希表访问链表的相应节点,修改val值,并将访问过的节点移到表头;如果没有则需要新建节点加到表头,同时哈希表中增加相应key值(当然,还需要检查链表长度还有无剩余,若是没有剩余则需要删去链表尾)。
//没有见过这个key,新值加入
if(!mp.containsKey(key)){ Node node = new Node(key, val);mp.put(key, node);//超出大小,移除最后一个if(size <= 0) removeLast();//大小还有剩余else //大小减1size--; //加到链表头insertFirst(node); 
}
//哈希表中已经有了,即链表里也已经有了
else{  mp.get(key).val = val;//访问过后,移到表头moveToHead(mp.get(key)); 
}
  • step 5:不管是新节点,还是访问过的节点都需要加到表头,若是访问过的,需要断开原来的连接,再插入表头head的后面。
//移到表头函数
void moveToHead(Node node){ //已经到了表头if(node.pre == head)  return;//将节点断开,取出来node.pre.next = node.next;node.next.pre = node.pre;//插入第一个前面insertFirst(node);
}
  • step 6:删除链表尾需要断掉尾节点前的连接,同时哈希表中去掉这个key值。
void removeLast(){ //哈希表去掉keymp.remove(tail.pre.key);//断连该节点tail.pre.pre.next = tail; tail.pre = tail.pre.pre;
}
  • step 7:如果是get操作,检验哈希表中有无这个key值,如果没有说明链表中也没有,返回-1,否则可以根据哈希表直接锁定链表中的位置进行访问,然后重复step 5,访问过的节点加入表头。
if(mp.containsKey(key)){Node node = mp.get(key);res = node.val;moveToHead(node);
}

图示:

alt

Java代码实现:

import java.util.*;
public class Solution {//设置双向链表结构static class Node{ int key;int val;Node pre;Node next;//初始化public Node(int key, int val) {this.key = key;this.val = val;this.pre = null;this.next = null;}}//哈希表private Map<Integer, Node> mp = new HashMap<>();//设置一个头private Node head = new Node(-1, -1); //设置一个尾private Node tail = new Node(-1, -1); private int size = 0; public int[] LRU (int[][] operators, int k) {//构建初始化连接//链表剩余大小this.size = k;this.head.next = this.tail;this.tail.pre = this.head;//获取操作数int len = (int)Arrays.stream(operators).filter(x -> x[0] == 2).count();int[] res = new int[len];//遍历所有操作for(int i = 0, j = 0; i < operators.length; i++){if(operators[i][0] == 1)//set操作set(operators[i][1], operators[i][2]);else//get操作res[j++] = get(operators[i][1]);}return res;}//插入函数private void set(int key, int val){//没有见过这个key,新值加入if(!mp.containsKey(key)){ Node node = new Node(key, val);mp.put(key, node);//超出大小,移除最后一个if(size <= 0) removeLast();//大小还有剩余else //大小减1size--; //加到链表头insertFirst(node); }//哈希表中已经有了,即链表里也已经有了else{  mp.get(key).val = val;//访问过后,移到表头moveToHead(mp.get(key)); }}//获取数据函数private int get(int key){int res = -1;if(mp.containsKey(key)){Node node = mp.get(key);res = node.val;moveToHead(node);}return res;}//移到表头函数private void moveToHead(Node node){ //已经到了表头if(node.pre == head)  return;//将节点断开,取出来node.pre.next = node.next;node.next.pre = node.pre;//插入第一个前面insertFirst(node);}//将节点插入表头函数private void insertFirst(Node node){ node.pre = head;node.next = head.next;head.next.pre = node;head.next = node;}//删去表尾函数,最近最少使用private void removeLast(){ //哈希表去掉keymp.remove(tail.pre.key);//断连该节点tail.pre.pre.next = tail; tail.pre = tail.pre.pre;}
}

C++代码实现

//双向链表
struct Node{ int key;int val;Node* pre;Node* next;//初始化Nodke(int k, int v): key(k), val(v), pre(NULL), next(NULL){}; 
};class Solution {
public:int size = 0;//双向链表头尾Node* head = NULL; Node* tail = NULL;//哈希表记录key值unordered_map<int, Node*> mp;vector<int> LRU(vector<vector<int> >& operators, int k) {vector<int> res;//构建初始化连接//链表剩余大小size = k; head = new Node(0, 0);tail = new Node(0, 0);head->next = tail;tail->pre = head;//操作数组为空if(operators.size() == 0) return res;//遍历所有操作for(int i = 0; i < operators.size(); i++){ auto op = operators[i];if(op[0] == 1) //set操作set(op[1], op[2]);else if(op[0] == 2) //get操作res.push_back(get(op[1]));}return res;}//插入函数void set(int key, int val){ //没有见过这个key,新值加入if(mp.find(key) == mp.end()){ Node* node = new Node(key, val);mp[key] = node;//超出大小,移除最后一个if(size <= 0) removeLast();//大小还有剩余else //大小减1size--; //加到链表头insertFirst(node); }//哈希表中已经有了,即链表里也已经有了else{  mp[key]->val = val;//访问过后,移到表头moveToHead(mp[key]); }}//获取数据函数int get(int key){ //找不到返回-1int res = -1; //哈希表中找到if(mp.find(key) != mp.end()){ //获取res = mp[key]->val;//访问过后移到表头moveToHead(mp[key]); }return res;}//移到表头函数void moveToHead(Node* node){ //已经到了表头if(node->pre == head)  return;//将节点断开,取出来node->pre->next = node->next;node->next->pre = node->pre;//插入第一个前面insertFirst(node);}//将节点插入表头函数void insertFirst(Node* node){ node->pre = head;node->next = head->next;head->next->pre = node;head->next = node;}//删去表尾函数,最近最少使用void removeLast(){ //哈希表去掉keymp.erase(tail->pre->key);//断连该节点tail->pre->pre->next = tail; tail->pre = tail->pre->pre;}
};

Python代码实现:

#构建双向链表
class Node:def __init__(self, key, val):self.key = keyself.val = valself.pre = Noneself.next = Noneclass Solution:def __init__(self):#双向链表头尾self.size = 0self.head = Noneself.tail = None#哈希表记录key值self.mp = dict()#将节点插入表头函数def insertFirst(self, node: Node):node.pre = self.headnode.next = self.head.nextself.head.next.pre = nodeself.head.next = node#移到表头函数def moveToHead(self, node: Node):#已经到了表头if node.pre == self.head:  return#将节点断开,取出来node.pre.next = node.nextnode.next.pre = node.pre#插入第一个前面self.insertFirst(node)#删去表尾函数,最近最少使用def removeLast(self):#哈希表去掉keyself.mp.pop(self.tail.pre.key)#断连该节点self.tail.pre.pre.next = self.tail; self.tail.pre = self.tail.pre.pre#插入函数def set(self, key: int, val: int):#没有见过这个key,新值加入if key not in self.mp:node = Node(key, val)self.mp[key] = node#超出大小,移除最后一个if self.size <= 0:self.removeLast()#大小还有剩余else:#大小减1self.size -= 1 #加到链表头self.insertFirst(node); #哈希表中已经有了,即链表里也已经有了else:self.mp[key].val = val#访问过后,移到表头self.moveToHead(self.mp[key])#获取数据函数def get(self, key: int) -> int:#找不到返回-1res = -1#哈希表中找到if key in self.mp:#获取res = self.mp[key].val#访问过后移到表头self.moveToHead(self.mp[key])return resdef LRU(self , operators: List[List[int]], k: int) -> List[int]:res = []#构建初始化连接#链表剩余大小self.size = k self.head = Node(0, 0)self.tail = Node(0, 0)self.head.next = self.tailself.tail.pre = self.head#遍历所有操作for i in range(len(operators)):op = operators[i]if op[0] == 1: #set操作self.set(op[1], op[2])else:#get操作res.append(self.get(op[1]))return res

复杂度分析:

  • 时间复杂度:\(O(n)\),其中\(n\)为操作数组大小,map为哈希表,每次插入的复杂度都是\(O(1)\)
  • 空间复杂度:\(O(k)\),链表和哈希表都是O(k)的辅助空间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/887486.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客题解 | 编辑距离(一)

牛客输入输出题单题解题目 题目链接 题目主要信息:给定两个长度可能不同的字符串,可以对第一个字符串增删改字符 求增删改的最少次数,让第一个字符串变成第二个字符串 字符串中只出现大小写字母举一反三: 学习完本题的思路你可以解决如下题目: BM65 最长公共子序列(二) BM6…

网上学习导航

推荐一个网上学习好玩的地方 学吧导航 网站截图

解决 windows 安装centos7虚拟机 每隔一段时间 虚拟机ip会变动 怎么固定他的ip

查看当前虚拟机的ip地址:我的是192.168.217.136然后获取到网关ip:打开 VMware Workstation,选中你要设置的虚拟机,点击 “编辑” -> “编辑虚拟机设置”。通过ipconfig 可以看到我的是ens160 我的文件就是 ifcfg-ens160 ,然后使用以下命令进行操作sudo nano /etc/sysco…

牛客题解 | 求二叉树的层序遍历

牛客输入输出题单题解题目 题目链接 题目的主要信息:将给定二叉树按行从上到下、从左到右的顺序输出 输出到一个二维数组中,数组中每行就是二叉树的一层举一反三: 学习完本题的思路你可以解决如下题目: BM27. 按之字形顺序打印二叉树 BM35. 判断是否是完全二叉树 方法一:非…

牛客题解 | 没有重复项数字的全排列

牛客输入输出题单题解题目 题目链接 题目主要信息:给定一个数组,求这组数字的全排列 数组无重复元素 以数字在数组中的位置靠前为优先级,按字典序排列输出举一反三: 学习完本题的思路你可以解决如下题目: BM56. 有重复项数字的全排列 BM58. 字符串的排列 BM60. 括号生成 递…

牛客题解 | 最长无重复子数组

牛客输入输出题单题解题目 题目链接 题目主要信息:题目给定一个数组,要找到其中最长的无重复的子数组的长度 子数组必须是数组中连续的一段举一反三: 学习完本题的思路你可以解决如下题目: BM90. 最小覆盖子串 方法:滑动窗口(推荐使用) 知识点1:滑动窗口 滑动窗口是指在数…

牛客题解 | 最长的括号子串

牛客输入输出题单题解题目 题目链接 题目主要信息:一个长度为\(n\)的仅包含左右括号的字符串 计算最长的格式正确的括号子串的长度举一反三: 学习完本题的思路你可以解决如下题目: BM65 最长公共子序列(二) BM66.最长公共子串 BM71.最长上升子序列(一) BM73 最长回文子串 BM…

牛客题解 | 最长公共前缀

牛客输入输出题单题解题目 题目链接 题目主要信息:给定一个字符串数组,其中有n个字符串,求所有字符串的最长公共前缀 公共前缀是指所有字符串都共有的前面部分的子串,从第一个字符开始举一反三: 学会了本题的思路,你将可以解决类似的字符串问题: BM83. 字符串变形 BM85. …

牛客题解 | 旋转数组

牛客输入输出题单题解题目 题目链接 题目主要信息:一个长度为\(n\)的数组,将数组整体循环右移\(m\)个位置(\(m\)可能大于\(n\)) 循环右移即最后\(m\)个元素放在数组最前面,前\(n-m\)个元素依次后移 不能使用额外的数组空间举一反三: 学习完本题的思路你可以解决如下题目:…

牛客题解 | 括号生成

牛客输入输出题单题解题目 题目链接 题目主要信息:求n对括号的全部合法组合,左右括号之间任意组合,只要合法就行 需要输出所有的结果举一反三: 学习完本题的思路你可以解决如下题目: BM55. 没有重复项数字的全排列 BM56. 有重复项数字的全排列 BM58. 字符串的排列 方法:递…

牛客题解 | 按之字形顺序打印二叉树

牛客输入输出题单题解题目 题目链接 题目的主要信息:给定一个二叉树,返回该二叉树的之字形层序遍 第一层从左向右,下一层从右向左,一直这样交替举一反三: 学习完本题的思路你可以解决如下题目: JZ32. 从上往下打印二叉树 JZ78. 把二叉树打印成多行 方法一:非递归层次遍历…

牛客题解 | 接雨水问题

牛客输入输出题单题解题目 题目链接 题目主要信息:给定一个整型数组,数组每个元素表示下图所示的每列灰色柱子高度,数值都是非负数 在雨水(图中蓝色部分)不超过边界的情况下,问最多能有多少蓝色的格子 数组以外的区域高度视为0举一反三: BM93. 盛水最多的容器 方法:双指…