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 呢?

一定要多做几遍!!

go 复制代码
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);
 */
相关推荐
流星白龙1 小时前
【Redis】4.基本全局命令
数据库·redis·缓存
流星白龙5 小时前
【Redis】3.Redis安装与命令行客户端
数据库·redis·缓存
我不想名字重复10 小时前
redis缓存和数据库数据保持一致
数据库·redis·缓存
时空无限10 小时前
vllm 大模型启动缓存相关环境变量 export
linux·缓存·vllm
流星白龙13 小时前
【Redis】6.计数其他命令
数据库·redis·缓存
正儿八经的少年14 小时前
redis 的大 key 和热 key 详解
数据库·redis·缓存
X-⃢_⃢-X15 小时前
十、Redis之布隆过滤器
数据库·redis·缓存
Database_Cool_16 小时前
阿里云 Tair(企业级内存数据库)vs 腾讯云 Redis 云缓存深度对比:性能/数据结构/成本全维度 Benchmark
数据库·阿里云·缓存
流星白龙16 小时前
【Redis】1.Redis特性
数据库·redis·缓存
chexus16 小时前
21. 深入 Nginx HTTP 缓存源码:CDN功能
nginx·http·缓存