Design - 460. LFU Cache
460. LFU Cache
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get
and put
.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value)
- Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Note that the number of times an item is used is the number of calls to the get
and put
functions for that item since it was inserted. This number is set to zero when the item is removed.
Follow up: Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.get(3); // returns 3.
cache.put(4, 4); // evicts key 1.
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
思路:
题目意思是要求实现一个数据结构,最小频率使用的高速缓存,LFU,是146题LRU的变形题,实现办法有很多,可以使用hashmap和bst来做,但是时间复杂度就是O(logK),题目提议是做到常数的时间复杂度,O(1),就只能用双向链表加上hashmap来做,因为双向链表移除一个元素的操作是O(1)的。
- 首先使用一个hashmap来存储key和节点对象。
- 而双向链表存储的是同一频率下的所有key,和lru类似,最近使用的放在链表头。
- 通过hashmap中的节点对象可以找到对应的双向循环链表中的位置,就可以移动key在频率链表中的位置,并且使用一个minFreq来记录最小频率的链表,这样在这个数据结构capacity满的时候,就能从最小频率的最久没使用过的位置删除数据节点。
代码:
go:
type LFUCache struct {
nodeMap map[int]*cacheNode // key-> api存入的value
listMap map[int]*doubleList // key-> freq
capacity int
minFreq int // 最小频率
}
type cacheNode struct {
key int
value int
freq int
keyNode *listNode
}
type listNode struct {
key int
next *listNode
prev *listNode
}
type doubleList struct {
head *listNode
tail *listNode
size int
}
func newList() *doubleList {
headNode := &listNode{}
tailNode := &listNode{}
headNode.next = tailNode
tailNode.prev = headNode
return &doubleList{head:headNode, tail:tailNode}
}
// 从双向循环链表中移除node节点
func (dl *doubleList) remove(node *listNode) {
node.prev.next = node.next
node.next.prev = node.prev
dl.size--
}
// 从双向循环链表中添加node节点到链表最前面
func (dl *doubleList) insert(node *listNode) {
dl.head.next.prev = node
node.next = dl.head.next
dl.head.next = node
node.prev = dl.head
dl.size++
}
// 移除最后一个元素,并取出
func (dl *doubleList) popback() *listNode {
listNode := dl.tail.prev
dl.remove(dl.tail.prev)
return listNode
}
func Constructor(capacity int) LFUCache {
return LFUCache {
nodeMap : make(map[int]*cacheNode),
listMap : make(map[int]*doubleList),
capacity : capacity,
}
}
func (this *LFUCache) Get(key int) int {
node, ok := this.nodeMap[key]
if !ok {
return -1
}
this.touch(node)
return node.value
}
func (this *LFUCache) touch(node *cacheNode) {
// 1. 更新频率
prevFreq := node.freq
node.freq++
freq := node.freq
// 2. 把节点在原来的频率list里面移除
dl := this.listMap[prevFreq]
dl.remove(node.keyNode)
if dl.size == 0 && prevFreq == this.minFreq {
// delete this list
delete(this.listMap, prevFreq)
this.minFreq++
}
// 3. 把节点插入到新的频率链表
dl, ok := this.listMap[freq]
if !ok {
dl = newList()
this.listMap[freq] = dl
}
dl.insert(node.keyNode)
}
func (this *LFUCache) Put(key int, value int) {
if this.capacity == 0 {
return
}
node, ok := this.nodeMap[key]
if ok {
// 更新value值
node.value = value
this.touch(node)
return
}
if len(this.nodeMap) == this.capacity {
// 没有容量需要删除最小频率的那个节点
// 删除最小频率节点
removeNode := (this.listMap[this.minFreq]).popback()
// 从nodeMap中移除
delete(this.nodeMap, removeNode.key)
}
// 添加新的节点
freq := 1
this.minFreq = freq
dl, ok := this.listMap[this.minFreq]
if !ok {
dl = newList()
this.listMap[this.minFreq] = dl
}
lNode := &listNode{key:key}
dl.insert(lNode)
this.nodeMap[key] = &cacheNode{
key : key,
value : value,
freq : freq,
keyNode : lNode,
}
}
/**
* Your LFUCache object will be instantiated and called as such:
* obj := Constructor(capacity);
* param_1 := obj.Get(key);
* obj.Put(key,value);
*/
- 通过自定义ServiceHost实现对WCF的扩展[实例篇]
- 通过自定义ServiceHost实现对WCF的扩展[原理篇]
- python使用rsa库做公钥解密(网上别处找不到)
- 通过“四大行为”对WCF的扩展[原理篇]
- WCF客户端运行时架构体系详解[下篇]
- WCF客户端运行时架构体系详解[上篇]
- WCF服务端运行时架构体系详解[续篇]
- [WCF-Discovery] 实例演示:如何利用服务发现机制实现服务的“动态”调用?
- [WCF-Discovery]服务如何能被”发现”
- 我的数据访问函数库的源代码(一)—— 共用部分
- 《WCF服务编程》关于“队列服务”一个值得商榷的地方
- 我的数据访问函数库的源代码(二)—— SQL语句部分
- 来源于WCF的设计模式:可扩展对象模式[上篇]
- 我的数据访问函数库的源代码(三)——返回结构数组
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- JAVA 初学者的编码规范一:命名风格与代码格式
- Leetcode No.11 盛最多水的容器
- OpenCV图像拼接函数vconcat()&hconcat()
- JAVA自定义注解
- [ 物联网篇 ] 28 - Linux ES7210 Driver 调试
- [ 利器篇 ] - Microsoft Surface Pro 系列安装 Ubuntu 16.04 系统
- 如何优雅的打造 All-in One 仓库
- matplotlib绘图教程:设置标签与图例
- 企业是如何从头开发一个商业项目的?
- 基于Haproxy的高可用实战
- 组复制常规操作-分布式恢复 | 全方位认识 MySQL 8.0 Group Replication
- 赞!7000 字学习笔记,MySQL 从入到放弃
- 面试官问我Volatile的原理?从操作系统层面的设计怼回去!
- 设计原则之单一职责
- 设计原则之开闭原则