← Назад к вопросам
LRU Cache
2.0 Middle🔥 131 комментариев
#Основы Go
Условие
Реализуйте LRU (Least Recently Used) кэш с фиксированной ёмкостью.
Интерфейс
type LRUCache struct {
// ваши поля
}
func NewLRUCache(capacity int) *LRUCache
func (c *LRUCache) Get(key int) (int, bool)
func (c *LRUCache) Put(key, value int)
Требования
- Get возвращает значение по ключу и обновляет его как недавно использованный
- Put добавляет или обновляет значение
- При превышении ёмкости вытесняется наименее недавно использованный элемент
- Операции Get и Put должны работать за O(1)
Пример
cache := NewLRUCache(2)
cache.Put(1, 1)
cache.Put(2, 2)
val, _ := cache.Get(1) // 1
cache.Put(3, 3) // вытесняет ключ 2
val, ok := cache.Get(2) // ok = false
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
LRU Cache - полное решение
Описание задачи
LRU (Least Recently Used) Cache - это кэширующая структура данных с фиксированной ёмкостью, которая автоматически вытесняет наименее недавно использованный элемент при переполнении. Это часто используется для оптимизации доступа к часто используемым данным.
Архитектурное решение
Для достижения O(1) сложности для обеих операций используем комбинацию двух структур данных:
- Двусвязный список (doubly-linked list) - для хранения элементов в порядке использования
- *HashMap (map[int]Node) - для быстрого поиска элемента за O(1)
Двусвязный список позволяет за O(1) переместить элемент в конец (самый свежий) и удалить элемент из начала (самый старый).
Реализация
package main
import "fmt"
type Node struct {
key, value int
prev, next *Node
}
type LRUCache struct {
capacity int
cache map[int]*Node
head *Node // dummy head для удобства
tail *Node // dummy tail для удобства
}
func NewLRUCache(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,
}
}
// Get возвращает значение по ключу и обновляет его как недавно использованный
func (c *LRUCache) Get(key int) (int, bool) {
node, exists := c.cache[key]
if !exists {
return 0, false
}
// Переместить в конец (самый свежий)
c.moveToEnd(node)
return node.value, true
}
// Put добавляет или обновляет значение в кэше
func (c *LRUCache) Put(key, value int) {
if node, exists := c.cache[key]; exists {
// Ключ уже существует - обновляем значение и перемещаем в конец
node.value = value
c.moveToEnd(node)
return
}
// Новый ключ
newNode := &Node{key: key, value: value}
c.cache[key] = newNode
c.addToEnd(newNode)
// Если превышена ёмкость - удаляем самый старый элемент
if len(c.cache) > c.capacity {
oldest := c.head.next
c.removeNode(oldest)
delete(c.cache, oldest.key)
}
}
// moveToEnd перемещает узел в конец (самый свежий)
func (c *LRUCache) moveToEnd(node *Node) {
c.removeNode(node)
c.addToEnd(node)
}
// addToEnd добавляет узел в конец (перед tail)
func (c *LRUCache) addToEnd(node *Node) {
prev := c.tail.prev
prev.next = node
node.prev = prev
node.next = c.tail
c.tail.prev = node
}
// removeNode удаляет узел из списка
func (c *LRUCache) removeNode(node *Node) {
prev := node.prev
next := node.next
prev.next = next
next.prev = prev
}
// Пример использования
func main() {
cache := NewLRUCache(2)
cache.Put(1, 1)
cache.Put(2, 2)
val, ok := cache.Get(1)
fmt.Printf("Get(1): %d, %v\\n", val, ok) // 1, true
cache.Put(3, 3) // Вытесняет ключ 2
val, ok = cache.Get(2)
fmt.Printf("Get(2): %v\\n", ok) // false
val, ok = cache.Get(3)
fmt.Printf("Get(3): %d, %v\\n", val, ok) // 3, true
}
Анализ сложности
-
Time:
Get(key)- O(1) (lookup в map + переместить в конец)Put(key, value)- O(1) (добавить/обновить + управление списком)
-
Space: O(capacity) - храним максимум
capacityэлементов
Ключевые моменты реализации
- Dummy узлы (head и tail) - упрощают код, избегаем nil checks при добавлении/удалении
- Двусвязный список - позволяет за O(1) удалить узел из любой позиции
- HashMap - обеспечивает O(1) lookup при получении значения
- Порядок элементов - head.next указывает на самый старый, tail.prev на самый свежий
Альтернативы
- Контейнер list - можно использовать встроенный пакет list вместо ручной реализации
- sync.Map с RWMutex - для многопоточного кэша требуется добавление синхронизации