一、题目
牛客题目链接:设计LRU缓存结构_牛客题霸_牛客网
题目描述:
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 capacity ,操作次数是 n ,并有如下功能:
1. Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
2. get(key):如果关键字 key 存在于缓存中,则返回key对应的value值,否则返回 -1 。
3. set(key, value):将记录(key, value)插入该结构,如果关键字 key 已经存在,则变更其数据值 value,如果不存在,则向缓存中插入该组 key-value ,如果key-value的数量超过capacity,弹出最久未使用的key-value
提示:
1.某个key的set或get操作一旦发生,则认为这个key的记录成了最常使用的,然后都会刷新缓存。
2.当缓存的大小超过capacity时,移除最不经常使用的记录。
3.返回的value都以字符串形式表达,如果是set,则会输出"null"来表示(不需要用户返回,系统会自动输出),方便观察。
4.函数set和get必须以O(1)的方式运行
5.为了方便区分缓存里key与value,下面说明的缓存里key用""号包裹
数据范围:
1≤capacity<=
0≤key,val≤2×
1≤n≤
示例1
输入:["set","set","get","set","get","set","get","get","get"],[[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]],2
返回值:["null","null","1","null","-1","null","-1","3","4"]
说明:我们将缓存看成一个队列,最后一个参数为2代表capacity,所以 Solution s = new Solution(2);
s.set(1,1); //将(1,1)插入缓存,缓存是{"1"=1},set操作返回"null"
s.set(2,2); //将(2,2)插入缓存,缓存是{"2"=2,"1"=1},set操作返回"null"
output=s.get(1);// 因为get(1)操作,缓存更新,缓存是{"1"=1,"2"=2},get操作返回"1"
s.set(3,3); //将(3,3)插入缓存,缓存容量是2,故去掉某尾的key-value,缓存是{"3"=3,"1"=1},set操作返回"null"
output=s.get(2);// 因为get(2)操作,不存在对应的key,故get操作返回"-1"
s.set(4,4); //将(4,4)插入缓存,缓存容量是2,故去掉某尾的key-value,缓存是{"4"=4,"3"=3},set操作返回"null"
output=s.get(1);// 因为get(1)操作,不存在对应的key,故get操作返回"-1"
output=s.get(3);//因为get(3)操作,缓存更新,缓存是{"3"=3,"4"=4},get操作返回"3"
output=s.get(4);//因为get(4)操作,缓存更新,缓存是{"4"=4,"3"=3},get操作返回"4"
二、解题思路&代码实现
方案一:哈希表+双向链表(推荐使用)
知识点1:哈希表
哈希表是一种根据关键码(key)直接访问值(value)的一种数据结构。而这种直接访问意味着只要知道key就能在O(1)时间内得到value,因此哈希表常用来统计频率、快速检验某个元素是否出现过等。
知识点2:双向链表
双向链表是一种特殊的链表,它除了链表具有的每个节点指向后一个节点的指针外,还拥有一个每个节点指向前一个节点的指针,因此它可以任意向前或者向后访问,每次更改节点连接状态的时候,需要变动两个指针。
解题思路:
插入与访问值都是O(1) ,没有任何一种数据结构可以直接做到。可以用哈希表+双向链表组合实现。
具体实现:
- step 1:构建一个双向链表的类,记录key值与val值,同时一前一后两个指针。用哈希表存储key值和链表节点,这样我们可以根据key值在哈希表中直接锁定链表节点,从而实现在链表中直接访问,能够做到O(1) 时间访问链表任意节点。
- step 2:设置全局变量,记录双向链表的头、尾及LRU剩余的大小,并全部初始化,首尾相互连接好。
- step 3:遍历函数的操作数组,检查第一个元素判断是属于set操作还是get操作。
- step 4:如果是set操作,即将key值与val值加入链表,我们先检查链表中是否有这个key值,可以通过哈希表检查出,如果有直接通过哈希表访问链表的相应节点,修改val值,并将访问过的节点移到表头;如果没有则需要新建节点加到表头,同时哈希表中增加相应key值(当然,还需要检查链表长度还有无剩余,若是没有剩余则需要删去链表尾)。
- step 5:不管是新节点,还是访问过的节点都需要加到表头,若是访问过的,需要断开原来的连接,再插入表头head的后面。
- step 6:删除链表尾需要断掉尾节点前的连接,同时哈希表中去掉这个key值。
- step 7:如果是get操作,检验哈希表中有无这个key值,如果没有说明链表中也没有,返回-1,否则可以根据哈希表直接锁定链表中的位置进行访问,然后重复step 5,访问过的节点加入表头。
复杂度分析:
- 时间复杂度:O(n) ,其中nn为操作数组大小,map为哈希表,每次插入的复杂度都是O(1)
- 空间复杂度:O(k) ,链表和哈希表都是O(k)的辅助空间
代码实现:
go:
package main
type Solution struct {
// write code here
Table map[int]*LinkNode
Capacity int
Len int
Head *LinkNode
Tail *LinkNode
}
type LinkNode struct {
Key int
Val int
Pre *LinkNode
Next *LinkNode
}
func Constructor(capacity int) Solution {
obj := Solution{
Table: make(map[int]*LinkNode, capacity),
Capacity: capacity,
Len: 0,
Head: nil,
Tail: nil,
}
return obj
}
func (this *Solution) get(key int) int {
// write code here
if node, ok := this.Table[key]; ok {
if node != this.Head { //将该节点移动到队首
if node.Next != nil { //将该节点移出
node.Next.Pre, node.Pre.Next = node.Pre, node.Next
} else {
this.Tail = node.Pre
this.Tail.Next = nil
}
this.Head.Pre = node
node.Next = this.Head
this.Head = node
this.Head.Pre = nil
} else { //本来在队首就无需处理移位
//do nothing
}
return node.Val
}
return -1
}
func (this *Solution) set(key int, value int) {
// write code here
if node, ok := this.Table[key]; ok {
if node != this.Head { //将该节点移动到队首
if node.Next != nil { //将该节点移出
node.Next.Pre, node.Pre.Next = node.Pre, node.Next
} else {
this.Tail = node.Pre
this.Tail.Next = nil
}
this.Head.Pre = node
node.Next = this.Head
this.Head = node
this.Head.Pre = nil
} else { //本来在队首就无需处理移位
//do nothing
}
node.Val = value
node.Key = key
} else {
if this.Len == this.Capacity { //删除尾部的节点
removeNode := this.Tail
if this.Tail == this.Head {
this.Head = nil
this.Tail = nil
} else {
this.Tail = this.Tail.Pre
this.Tail.Next = nil
}
delete(this.Table, removeNode.Key)
this.Len--
}
node := &LinkNode{
Key: key,
Val: value,
Pre: nil,
Next: this.Head,
}
if this.Head != nil {
this.Head.Pre = node
}
this.Head = node
this.Table[key] = node
this.Len++
if this.Tail == nil {
this.Tail = node
}
}
}
/**
* Your Solution object will be instantiated and called as such:
* solution := Constructor(capacity);
* output := solution.get(key);
* solution.set(key,value);
*/
java:
import java.util.*;
//双向链表
class Node {
int key ;//键
int val ;//值
Node pre ;
Node nxt ;
Node(int key, int val) {
this.key = key ;
this.val = val ;
}
}
public class Solution {
HashMap<Integer, Node> map ; //存储数据+查询
int maxSize ;//最大容量
int size ;//当前容量
//双向链表保存数据的访问时间情况,链头为最近使用,链尾为最久未使用
Node head_pre
;//最近使用的结点 的上一个结点(辅助结点,免去考虑在链头、链尾操作的特殊情况)
Node tail_nxt ;//最久未使用的结点的 上一个结点(辅助结点)
public Solution(int capacity) {
map = new HashMap<>() ;
this.size = 0 ;
this.maxSize = capacity ;
head_pre = new Node(-1, -1) ;
tail_nxt = new Node(-1, -1) ;
head_pre.nxt = tail_nxt ;//构建双向链表
tail_nxt.pre = head_pre ;
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1 ;
} else {
Node find = map.get(key) ;
moveToHead(find)
;//将访问的节点移动到最近使用的位置(即,链表头)
return find.val ;
}
}
public void set(int key, int value) {
if (map.containsKey(key)) {
Node find = map.get(key) ;
find.val = value ;
moveToHead(find) ;
} else {
Node newNode = new Node(key, value) ;
if (this.size == this.maxSize) {
Node last = removeTail() ;//移除最久未使用的
map.remove(last.key) ;
map.put(key, newNode) ;
insertHead(newNode) ;
} else {
map.put(key, newNode) ;
insertHead(newNode) ;
this.size ++ ;
}
}
}
//将节点移动到最近使用的位置(即,链表头)
public void moveToHead(Node node) {
if (node.pre == head_pre) {
return ;
} else {
node.pre.nxt = node.nxt ;
node.nxt.pre = node.pre ;
node.nxt = null ;
node.pre = null ;
insertHead(node) ;
}
}
//在链表头插入
public void insertHead(Node node) {
node.nxt = head_pre.nxt ;
head_pre.nxt = node ;
node.nxt.pre = node ;
node.pre = head_pre ;
}
//移除最后一个结点(最久未使用的),然后返回这个结点
public Node removeTail() {
if (head_pre.nxt == tail_nxt) return null ;
Node target = tail_nxt.pre ;
target.pre.nxt = target.nxt ;
target.nxt.pre = target.pre ;
target.pre = null ;
target.nxt = null ;
return target ;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution solution = new Solution(capacity);
* int output = solution.get(key);
* solution.set(key,value);
*/