1492 字
7 分钟
LFU 缓存

题目描述
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。
实现 LFUCache
类:
LFUCache(int capacity)
- 用数据结构的容量capacity
初始化对象int get(int key)
- 如果键key
存在于缓存中,则获取键的值,否则返回-1
。void put(int key, int value)
- 如果键key
已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量capacity
时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。
为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 1
(由于 put 操作)。对缓存中的键执行 get
或 put
操作,使用计数器的值将会递增。
函数 get
和 put
必须以 O(1)
的平均时间复杂度运行。
解题思路
- 双哈希表结构:
key_map
:存储键到节点(值+频率)的映射freq_map
:存储频率到双向链表的映射
- 双向链表:
- 每个频率对应一个链表,维护节点访问时序
- 链表头为最近访问,链表尾为最久未访问
- 最小频率追踪:
- 动态维护当前最小使用频率
min_freq
- 动态维护当前最小使用频率
- 操作处理:
get
:更新节点频率,调整链表位置put
:插入新节点或更新现有节点
- 淘汰机制:
- 缓存满时删除
min_freq
链表尾节点
- 缓存满时删除
关键洞察
- 频率分层:
- 相同频率节点通过双向链表维护时序
- O(1)操作:
- 哈希表实现常数时间节点访问
- 链表实现常数时间节点增删
- 最小频率更新:
- 插入新节点时
min_freq=1
- 删除节点后检查链表空则
min_freq++
- 插入新节点时
- 原子操作:
- 节点频率更新需先移除后重新插入
- 高效淘汰:
- 直接访问
min_freq
链表尾节点删除
- 直接访问
复杂度分析
指标 | 说明 |
---|---|
时间复杂度 | O(1):get/put 操作均为常数时间 |
空间复杂度 | O(n):存储所有节点和链表结构 |
最优情况 | O(1):缓存未满时操作 |
最坏情况 | O(1):缓存满时淘汰操作 |
代码实现
class Node { constructor(key, value) { this.key = key; this.value = value; this.freq = 1; this.prev = null; this.next = null; }}
class DoublyLinkedList { constructor() { this.head = new Node(null, null); this.tail = new Node(null, null); this.head.next = this.tail; this.tail.prev = this.head; this.size = 0; }
addToHead(node) { node.next = this.head.next; node.prev = this.head; this.head.next.prev = node; this.head.next = node; this.size++; }
remove(node) { node.prev.next = node.next; node.next.prev = node.prev; this.size--; }
removeTail() { if (this.size === 0) return null; const node = this.tail.prev; this.remove(node); return node; }}
class LFUCache { constructor(capacity) { this.capacity = capacity; this.minFreq = 0; this.size = 0; this.keyMap = new Map(); this.freqMap = new Map(); }
get(key) { if (!this.keyMap.has(key)) return -1; const node = this.keyMap.get(key); this.updateNode(node); return node.value; }
put(key, value) { if (this.capacity === 0) return;
if (this.keyMap.has(key)) { const node = this.keyMap.get(key); node.value = value; this.updateNode(node); return; }
if (this.size >= this.capacity) { const minList = this.freqMap.get(this.minFreq); const deleted = minList.removeTail(); this.keyMap.delete(deleted.key); this.size--; }
const newNode = new Node(key, value); this.keyMap.set(key, newNode);
if (!this.freqMap.has(1)) { this.freqMap.set(1, new DoublyLinkedList()); } this.freqMap.get(1).addToHead(newNode); this.minFreq = 1; this.size++; }
updateNode(node) { const oldFreq = node.freq; const oldList = this.freqMap.get(oldFreq); oldList.remove(node);
if (oldList.size === 0 && oldFreq === this.minFreq) { this.minFreq++; }
node.freq++; const newFreq = node.freq;
if (!this.freqMap.has(newFreq)) { this.freqMap.set(newFreq, new DoublyLinkedList()); } this.freqMap.get(newFreq).addToHead(node); }}
use std::collections::HashMap;use std::rc::Rc;use std::cell::RefCell;
struct Node { key: i32, value: i32, freq: i32, prev: Option<Rc<RefCell<Node>>>, next: Option<Rc<RefCell<Node>>>,}
impl Node { fn new(key: i32, value: i32) -> Self { Node { key, value, freq: 1, prev: None, next: None, } }}
struct DoublyLinkedList { head: Rc<RefCell<Node>>, tail: Rc<RefCell<Node>>, size: usize,}
impl DoublyLinkedList { fn new() -> Self { let head = Rc::new(RefCell::new(Node::new(0, 0))); let tail = Rc::new(RefCell::new(Node::new(0, 0))); head.borrow_mut().next = Some(tail.clone()); tail.borrow_mut().prev = Some(head.clone()); DoublyLinkedList { head, tail, size: 0 } }
fn add_to_head(&mut self, node: Rc<RefCell<Node>>) { let next = self.head.borrow().next.clone(); node.borrow_mut().prev = Some(self.head.clone()); node.borrow_mut().next = next.clone(); self.head.borrow_mut().next = Some(node.clone()); next.unwrap().borrow_mut().prev = Some(node.clone()); self.size += 1; }
fn remove(&mut self, node: Rc<RefCell<Node>>) { let prev = node.borrow().prev.clone().unwrap(); let next = node.borrow().next.clone().unwrap(); prev.borrow_mut().next = Some(next.clone()); next.borrow_mut().prev = Some(prev); self.size -= 1; }
fn remove_tail(&mut self) -> Option<Rc<RefCell<Node>>> { if self.size == 0 { return None; } let node = self.tail.borrow().prev.clone().unwrap(); self.remove(node.clone()); Some(node) }}
struct LFUCache { capacity: usize, min_freq: i32, size: usize, key_map: HashMap<i32, Rc<RefCell<Node>>>, freq_map: HashMap<i32, DoublyLinkedList>,}
impl LFUCache { fn new(capacity: i32) -> Self { LFUCache { capacity: capacity as usize, min_freq: 0, size: 0, key_map: HashMap::new(), freq_map: HashMap::new(), } }
fn get(&mut self, key: i32) -> i32 { match self.key_map.get(&key) { Some(node) => { let node = node.clone(); self.update_node(node.clone()); node.borrow().value }, None => -1, } }
fn put(&mut self, key: i32, value: i32) { if self.capacity == 0 { return; }
if let Some(node) = self.key_map.get(&key) { let node = node.clone(); node.borrow_mut().value = value; self.update_node(node); return; }
if self.size >= self.capacity { if let Some(list) = self.freq_map.get_mut(&self.min_freq) { if let Some(tail) = list.remove_tail() { self.key_map.remove(&tail.borrow().key); self.size -= 1; } } }
let new_node = Rc::new(RefCell::new(Node::new(key, value))); self.key_map.insert(key, new_node.clone());
self.freq_map.entry(1) .or_insert_with(DoublyLinkedList::new) .add_to_head(new_node);
self.min_freq = 1; self.size += 1; }
fn update_node(&mut self, node: Rc<RefCell<Node>>) { let old_freq = node.borrow().freq; let new_freq = old_freq + 1;
if let Some(list) = self.freq_map.get_mut(&old_freq) { list.remove(node.clone());
if list.size == 0 && old_freq == self.min_freq { self.min_freq += 1; } }
node.borrow_mut().freq = new_freq; self.freq_map.entry(new_freq) .or_insert_with(DoublyLinkedList::new) .add_to_head(node); }}
class Node: __slots__ = ('key', 'value', 'freq', 'prev', 'next') def __init__(self, key, value): self.key = key self.value = value self.freq = 1 self.prev = None self.next = None
class DoublyLinkedList: def __init__(self): self.head = Node(0, 0) self.tail = Node(0, 0) self.head.next = self.tail self.tail.prev = self.head self.size = 0
def add_to_head(self, node): node.next = self.head.next node.prev = self.head self.head.next.prev = node self.head.next = node self.size += 1
def remove(self, node): node.prev.next = node.next node.next.prev = node.prev self.size -= 1
def remove_tail(self): if self.size == 0: return None node = self.tail.prev self.remove(node) return node
class LFUCache: def __init__(self, capacity: int): self.capacity = capacity self.min_freq = 0 self.size = 0 self.key_map = {} self.freq_map = {}
def get(self, key: int) -> int: if key not in self.key_map: return -1 node = self.key_map[key] self._update_node(node) return node.value
def put(self, key: int, value: int) -> None: if self.capacity == 0: return
if key in self.key_map: node = self.key_map[key] node.value = value self._update_node(node) return
if self.size >= self.capacity: min_list = self.freq_map.get(self.min_freq) if min_list and min_list.size > 0: deleted = min_list.remove_tail() if deleted: self.key_map.pop(deleted.key) self.size -= 1
new_node = Node(key, value) self.key_map[key] = new_node
if 1 not in self.freq_map: self.freq_map[1] = DoublyLinkedList() self.freq_map[1].add_to_head(new_node) self.min_freq = 1 self.size += 1
def _update_node(self, node): old_freq = node.freq new_freq = old_freq + 1
if old_freq in self.freq_map: old_list = self.freq_map[old_freq] old_list.remove(node)
if old_list.size == 0 and old_freq == self.min_freq: self.min_freq += 1
node.freq = new_freq
if new_freq not in self.freq_map: self.freq_map[new_freq] = DoublyLinkedList() self.freq_map[new_freq].add_to_head(node)
实际应用场景
- 数据库缓存:MySQL查询结果缓存`
- CDN系统: 内容分发网络的热点资源管理`
- 操作系统:内存页面置换算法`
- 推荐系统:用户行为频率跟踪`