Java 编程每日一题:实现一个简易的 LRU 缓存

题目描述

设计一个 LRU(最近最少使用)缓存结构,支持以下操作:

  • get(key):如果 key 存在于缓存中,则获取 key 对应的 value;否则返回 - 1
  • put(key, value):如果 key 不存在,则插入该键值对;如果 key 已存在,则更新 value;如果缓存满了,则删除最久未使用的项后再插入

要求:get 和 put 操作的时间复杂度均为 O (1)

解题思路

要实现一个高效的 LRU 缓存,我们需要兼顾快速查找和快速插入删除的需求。可以采用以下数据结构组合:

  1. 哈希表(HashMap):用于快速查找节点,键为缓存的 key,值为双向链表的节点引用
  2. 双向链表:用于维护节点的访问顺序,最近使用的节点移到链表头部,最久未使用的节点在链表尾部

这样组合的好处是:

  • 哈希表提供 O (1) 时间复杂度的查找
  • 双向链表提供 O (1)
  • 时间复杂度的插入和删除

import java.util.HashMap;

import java.util.Map;

public class LRUCache {

// 双向链表节点

static class Node {

int key;

int value;

Node prev;

Node next;

public Node(int key, int value) {

this.key = key;

this.value = value;

}

}

private final int capacity;

private final Map<Integer, Node> cache;

// 虚拟头节点和尾节点,简化边界处理

private final Node head;

private final Node tail;

public LRUCache(int capacity) {

this.capacity = capacity;

this.cache = new HashMap<>(capacity);

// 初始化虚拟节点

head = new Node(0, 0);

tail = new Node(0, 0);

head.next = tail;

tail.prev = head;

}

// 获取缓存值

public int get(int key) {

if (!cache.containsKey(key)) {

return -1;

}

Node node = cache.get(key);

// 将访问的节点移到头部,表示最近使用

moveToHead(node);

return node.value;

}

// 放入缓存

public void put(int key, int value) {

if (cache.containsKey(key)) {

// 键已存在,更新值并移到头部

Node node = cache.get(key);

node.value = value;

moveToHead(node);

} else {

// 键不存在,创建新节点

Node newNode = new Node(key, value);

cache.put(key, newNode);

// 添加到头部

addToHead(newNode);

// 如果超出容量,删除尾部节点

if (cache.size() > capacity) {

Node tailNode = removeTail();

cache.remove(tailNode.key);

}

}

}

// 将节点移到头部

private void moveToHead(Node node) {

removeNode(node);

addToHead(node);

}

// 移除节点

private void removeNode(Node node) {

node.prev.next = node.next;

node.next.prev = node.prev;

}

// 添加节点到头部

private void addToHead(Node node) {

node.next = head.next;

node.prev = head;

head.next.prev = node;

head.next = node;

}

// 移除尾部节点(最久未使用)

private Node removeTail() {

Node res = tail.prev;

removeNode(res);

return res;

}

// 测试方法

public static void main(String[] args) {

LRUCache cache = new LRUCache(2);

cache.put(1, 1);

cache.put(2, 2);

System.out.println(cache.get(1)); // 输出 1

cache.put(3, 3); // 超出容量,删除key=2

System.out.println(cache.get(2)); // 输出 -1

cache.put(4, 4); // 超出容量,删除key=1

System.out.println(cache.get(1)); // 输出 -1

System.out.println(cache.get(3)); // 输出 3

System.out.println(cache.get(4)); // 输出 4

}

}