Medium
题目描述
设计一个遵循最近最少使用 (LRU) 缓存约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity)以正整数作为容量capacity初始化 LRU 缓存int get(int key)如果关键字key存在于缓存中,则返回关键字的值,否则返回-1void put(int key, int value)如果关键字key已经存在,则变更其数据值value;如果不存在,则向缓存中插入该组key-value。如果插入操作导致关键字数量超过capacity,则应该逐出最近最少使用的关键字
函数 get 和 put 必须以 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
提示:
1 <= capacity <= 30000 <= key <= 10^40 <= value <= 10^5- 最多调用
2 * 10^5次get和put
解题思路
解题思路
LRU缓存需要同时支持快速查找和维护访问顺序,这需要结合两种数据结构来实现:
核心思想
- 哈希表:实现O(1)时间的查找操作
- 双向链表:维护元素的访问顺序,支持O(1)时间的插入和删除
设计要点
- 双向链表的头部表示最近使用的元素,尾部表示最久未使用的元素
- 哈希表存储key到链表节点的映射关系
- 每次访问元素时,将其移动到链表头部
- 当容量满时,删除链表尾部元素
操作流程
get操作:
- 在哈希表中查找key
- 如果存在,将对应节点移动到链表头部,返回值
- 如果不存在,返回-1
put操作:
- 如果key已存在,更新值并移动到头部
- 如果key不存在:
- 创建新节点并插入到头部
- 如果超过容量,删除尾部节点并从哈希表中移除
这种设计保证了所有操作都能在O(1)时间内完成。
代码实现
class LRUCache {
private:
struct Node {
int key, value;
Node* prev;
Node* next;
Node() : key(0), value(0), prev(nullptr), next(nullptr) {}
Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
};
unordered_map<int, Node*> cache;
Node* head;
Node* tail;
int capacity;
void addToHead(Node* node) {
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void removeNode(Node* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
void moveToHead(Node* node) {
removeNode(node);
addToHead(node);
}
Node* removeTail() {
Node* last = tail->prev;
removeNode(last);
return last;
}
public:
LRUCache(int capacity) {
this->capacity = capacity;
head = new Node();
tail = new Node();
head->next = tail;
tail->prev = head;
}
int get(int key) {
Node* node = cache[key];
if (node == nullptr) {
return -1;
}
moveToHead(node);
return node->value;
}
void put(int key, int value) {
Node* node = cache[key];
if (node == nullptr) {
Node* newNode = new Node(key, value);
cache[key] = newNode;
addToHead(newNode);
if (cache.size() > capacity) {
Node* tail = removeTail();
cache.erase(tail->key);
delete tail;
}
} else {
node->value = value;
moveToHead(node);
}
}
};
class LRUCache:
class Node:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = {}
# 创建虚拟头尾节点
self.head = self.Node()
self.tail = self.Node()
self.head.next = self.tail
self.tail.prev = self.head
def add_to_head(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def remove_node(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def move_to_head(self, node):
self.remove_node(node)
self.add_to_head(node)
def remove_tail(self):
last = self.tail.prev
self.remove_node(last)
return last
def get(self, key: int) -> int:
node = self.cache.get(key)
if not node:
return -1
self.move_to_head(node)
return node.value
def put(self, key: int, value: int) -> None:
node = self.cache.get(key)
if not node:
new_node = self.Node(key, value)
self.cache[key] = new_node
self.add_to_head(new_node)
if len(self.cache) > self.capacity:
tail = self.remove_tail()
del self.cache[tail.key]
else:
node.value = value
self.move_to_head(node)
public class LRUCache {
private class Node {
public int Key { get; set; }
public int Value { get; set; }
public Node Prev { get; set; }
public Node Next { get; set; }
public Node(int key = 0, int value = 0) {
Key = key;
Value = value;
}
}
private Dictionary<int, Node> cache;
private Node head;
private Node tail;
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
cache = new Dictionary<int, Node>();
head = new Node();
tail = new Node();
head.Next = tail;
tail.Prev = head;
}
private void AddToHead(Node node) {
node.Prev = head;
node.Next = head.Next;
head.Next.Prev = node;
head.Next = node;
}
private void RemoveNode(Node node) {
node.Prev.Next = node.Next;
node.Next.Prev = node.Prev;
}
private void MoveToHead(Node node) {
RemoveNode(node);
AddToHead(node);
}
private Node RemoveTail() {
Node last = tail.Prev;
RemoveNode(last);
return last;
}
public int Get(int key) {
if (cache.TryGetValue(key, out Node node)) {
MoveToHead(node);
return node.Value;
}
return -1;
}
public void Put(int key, int value) {
if (cache.TryGetValue(key, out Node node)) {
node.Value = value;
MoveToHead(node);
} else {
Node newNode = new Node(key, value);
cache[key] = newNode;
AddToHead(newNode);
if (cache.Count > capacity) {
Node tail = RemoveTail();
cache.Remove(tail.Key);
}
}
}
}
var LRUCache = function(capacity) {
this.capacity = capacity;
this.cache = new Map();
};
LRUCache.prototype.get = function(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return -1;
};
LRUCache.prototype.put = function(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
};
复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| get | O(1) | O(capacity) |
| put | O(1) | O(capacity) |
说明:
- 时间复杂度:哈希表的查找、插入、删除都是O(1),双向链表的插入、删除也是O(1)
- 空间复杂度:需要存储最多capacity个节点,每个节点占用常数空间
相关题目
. LFU Cache (Hard)
. Design Most Recently Used Queue (Medium)