当前位置: 首页 > article >正文

LRU 缓存

LRU 缓存

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 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

题解:

​ 常做常新的一道题,其实有点偏向于模板题目了,没有做过上来直接写会很抓瞎,有一些常见的问题

  • 为什么用双向链表而不是单向链表? 将某个节点移动到链表头部或者将链表尾部节点删去,都要用到删除链表中某个节点这个操作。你想要删除链表中的某个节点,需要找到该节点的前驱节点和后继节点。对于寻找后继节点,单向链表和双向链表都能通过 next 指针在O(1)时间内完成;对于寻找前驱节点,单向链表需要从头开始找,也就是要O(n)时间,双向链表可以通过前向指针直接找到,需要O(1)时间。综上,要想在O(1)时间内完成该操作,当然需要双向链表,实际上就是用双向链表空间换时间了。
  • 为什么链表节点需要同时存储 key 和 value,而不是仅仅只存储 value? 因为删去最近最少使用的键值对时,要删除链表的尾节点,如果节点中没有存储 key,那么怎么知道是哪个 key 被删除,进而在 map 中删去该 key 对应的 key-value 呢?

一定要多做几遍!!

type LRUCache struct {
	size, capacity int
	cache          map[int]*Node
	head, tail     *Node
}

type Node struct {
	value, key int
	prev, next *Node
}

func Constructor(capacity int) LRUCache {
	head := &Node{}
	tail := &Node{}
	head.next = tail
	tail.prev = head

	return LRUCache{
		capacity: capacity,
		cache:    make(map[int]*Node),
		head:     head,
		tail:     tail,
	}
}

func (this *LRUCache) Get(key int) int {
	// 查询关键字 key 存在于缓存中,返回关键字的值,不存在则返回 -1
	if node, exist := this.cache[key]; exist {
		this.moveToHead(node)
		return node.value
	}
	return -1
}

func (this *LRUCache) Put(key int, value int) {
	// 如果关键字 key 已经存在,则变更其数据值 value ;
	// 如果不存在,则向缓存中插入该组 key-value 。
	// 如果插入操作导致关键字数量超过 capacity ,则逐出最久未使用的关键字
	if node, exist := this.cache[key]; exist {
		node.value = value
		this.moveToHead(node)
	} else {
		newNode := &Node{key: key, value: value}
		this.cache[key] = newNode
		this.addToHead(newNode)
		this.size++
		if this.size > this.capacity {
			tail := this.removeTail()
			delete(this.cache, tail.key)
			this.size--
		}
	}
}

// Put 操作元素,如果不存在向缓存中添加
func (this *LRUCache) addToHead(node *Node) {
	node.prev = this.head
	node.next = this.head.next
	this.head.next.prev = node
	this.head.next = node
}

// 本来在队列中的元素,最近访问(Get, Put)后移动到队首
func (this *LRUCache) moveToHead(node *Node) {
	this.removeNode(node)
	this.addToHead(node)
}

// Put 操作插入,超出容量则移除节点
func (this *LRUCache) removeNode(node *Node) {
	node.prev.next = node.next
	node.next.prev = node.prev
}

// 移除最近最久未使用的节点
func (this *LRUCache) removeTail() *Node {
	node := this.tail.prev
	this.removeNode(node)
	return node
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * obj := Constructor(capacity);
 * param_1 := obj.Get(key);
 * obj.Put(key,value);
 */

http://www.kler.cn/a/452824.html

相关文章:

  • 普通人怎么入门学习并使用AI?
  • 我的Opencv
  • 机器学习系列(一)——K-近邻算法
  • Node.js 工具:在 Windows 11 中配置 Node.js 的详细步骤
  • Unity3D Huatuo技术原理剖析详解
  • GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台
  • 【Apache Paimon】-- 11 -- Flink 消费 kakfa 写 S3 File
  • 全局JDK环境和ES自带的JDK混用导致的ES集群创建失败
  • Spring Boot 知识要点全解析
  • 04.HTTPS的实现原理-HTTPS的混合加密流程
  • 【魅力golang】之-玩转协程
  • Qt之QML应用程序开发:给应用程序添加图标文件
  • 【FastAPI】日志
  • element ui--下拉根据拼音首字母过滤
  • 纯真社区版IP库CZDB数据格式使用教程
  • 05.HTTPS的实现原理-HTTPS的握手流程(TLS1.2)
  • 【大语言模型】ACL2024论文-34 你的模型能区分否定和隐含意义吗?通过意图编码器揭开挑战
  • 美食推荐系统|Java|SSM|JSP|
  • w~视觉~3D~合集5
  • 如何编写 Prompt
  • 笔记工具--MD-Markdown的语法技巧
  • OSI 网络 7 层模型
  • Let‘s encrypt 免费 SSL 证书安装
  • [Unity Shader][图形渲染]【游戏开发】 Shader数学基础8 - 齐次坐标
  • Docker部署Sentinel
  • vue 基础学习