当前位置: 首页 > news >正文

阿里巴巴高级Java工程师面试算法真题解析:LRU Cache实现

文章目录

    • 前言
    • 题目描述
    • 解题思路
    • 实现方案
      • 复杂度分析
      • 代码实现
      • 测试用例
    • 总结

前言

在互联网行业的技术面试中,尤其是针对高级Java工程师岗位,算法和数据结构的考察往往是决定性的一环。作为BAT(百度、阿里巴巴、腾讯)等一线互联网公司面试中的经典题目,LRU(Least Recently Used)缓存机制不仅体现了候选人对基础数据结构的掌握程度,更考察了其在实际场景中解决复杂问题的能力。本文将从题目解析、解题思路、代码实现、复杂度分析等多个维度,深入剖析LRU缓存的实现原理,并提供完整的Java代码示例和测试用例,帮助全面掌握这一经典算法题。

在这里插入图片描述

题目描述

设计并实现一个LRU(Least Recently Used)缓存机制,要求实现以下接口:

class LRUCache {public LRUCache(int capacity) { ... } // 初始化缓存容量public int get(int key) { ... }       // 获取缓存中的值public void put(int key, int value) { ... } // 放入键值对到缓存中
}

要求:
get(key) - 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1。
put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。
当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
所有操作必须在 O(1) 时间复杂度内完成。

解题思路

LRU缓存是一种常用的缓存淘汰策略,其核心思想是:当缓存满时,优先淘汰最久未被使用的数据。
要实现O(1)时间复杂度的get和put操作,我们需要结合两种数据结构:
哈希表(HashMap) - 用于O(1)时间复杂度的查找操作
双向链表(Doubly Linked List) - 用于O(1)时间复杂度的插入和删除操作

面试官往往会通过这道题目,深入考察候选人的以下能力:
基础数据结构掌握程度:能否熟练运用哈希表、链表等基础数据结构
算法优化能力:如何在满足功能需求的前提下,将时间复杂度优化到O(1)
代码实现能力:代码的规范性、可读性以及边界条件的处理
系统设计思维:是否考虑到并发安全、扩展性等实际应用场景中的问题

实现方案

使用哈希表存储key到链表节点的映射,实现O(1)查找
使用双向链表维护访问顺序,最近访问的节点放在头部,最久未访问的节点放在尾部
当访问节点时,将其移动到链表头部
当缓存满时,删除链表尾部节点

复杂度分析

时间复杂度:对于 put 和 get 操作,时间复杂度都是 O(1),因为:
哈希表的查找、插入、删除操作都是 O(1)
双向链表的插入和删除操作也是 O(1)
空间复杂度:O(capacity),因为哈希表和双向链表最多存储 capacity+1 个元素

代码实现

/*** LRUCache* @author senfel* @version 1.0* @date 2025/8/8 14:23*/
public class LRUCache {// 定义双向链表节点class DLinkedNode {int key;int value;DLinkedNode prev;DLinkedNode next;public DLinkedNode() {}public DLinkedNode(int key, int value) {this.key = key;this.value = value;}}// 缓存容量private int capacity;// 当前缓存大小private int size;// 哈希表,存储key到节点的映射private Map<Integer, DLinkedNode> cache = new HashMap<>();// 伪头部和伪尾部节点private DLinkedNode head, tail;public LRUCache(int capacity) {this.capacity = capacity;this.size = 0;// 初始化伪头部和伪尾部节点head = new DLinkedNode();tail = new DLinkedNode();// 连接伪头部和伪尾部head.next = tail;tail.prev = head;}public int get(int key) {DLinkedNode node = cache.get(key);if (node == null) {return -1;}// 如果key存在,先通过哈希表定位,再移到头部moveToHead(node);return node.value;}public void put(int key, int value) {DLinkedNode node = cache.get(key);if (node == null) {// 如果key不存在,创建一个新的节点DLinkedNode newNode = new DLinkedNode(key, value);// 添加进哈希表cache.put(key, newNode);// 添加至双向链表的头部addToHead(newNode);++size;if (size > capacity) {// 如果超出容量,删除双向链表的尾部节点DLinkedNode tail = removeTail();// 删除哈希表中对应的项cache.remove(tail.key);--size;}} else {// 如果key存在,先通过哈希表定位,将对应的节点的值更新为valuenode.value = value;// 并将该节点移到双向链表的头部moveToHead(node);}}// 将节点添加到双向链表头部private void addToHead(DLinkedNode node) {node.prev = head;node.next = head.next;head.next.prev = node;head.next = node;}// 删除节点private void removeNode(DLinkedNode node) {node.prev.next = node.next;node.next.prev = node.prev;}// 将节点移动到头部(先删除再添加到头部)private void moveToHead(DLinkedNode node) {removeNode(node);addToHead(node);}// 删除尾部节点并返回该节点private DLinkedNode removeTail() {DLinkedNode res = tail.prev;removeNode(res);return res;}
}

测试用例

public class LRUCacheTest {public static void main(String[] args) {LRUCache cache = new LRUCache(2);cache.put(1, 1);  // 缓存是 {1=1}cache.put(2, 2);  // 缓存是 {1=1, 2=2}System.out.println(cache.get(1)); // 返回 1cache.put(3, 3);  // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}System.out.println(cache.get(2)); // 返回 -1 (未找到)cache.put(4, 4);  // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}System.out.println(cache.get(1)); // 返回 -1 (未找到)System.out.println(cache.get(3)); // 返回 3System.out.println(cache.get(4)); // 返回 4}
}

总结

LRU缓存是面试中的经典题目,它考察了候选人对数据结构的掌握程度以及对时间复杂度的优化能力。通过哈希表和双向链表的组合,我们实现了O(1)时间复杂度的LRU缓存,在实际工作中也有广泛的应用场景。掌握这类算法题不仅能帮助我们在面试中脱颖而出,更重要的是培养了我们解决实际问题的能力。在日常开发中,我们也会经常遇到类似的缓存设计问题,理解其原理有助于我们设计出更高效的系统。

http://www.dtcms.com/a/321841.html

相关文章:

  • 详解 RT-Thread 串口一配置、设备查找与打印功能(rt_kprintf)的绑定机制
  • 完整设计 之 运行时九宫格 (太乙九宫 播放器)
  • AI 记忆管理系统:工程实现设计方案
  • 【感知机】感知机(perceptron)学习算法知识点汇总
  • 代码随想录算法训练营第三十八天、三十九天|动态规划part11、12
  • 【LLM开发学习】
  • 小程序实现二维码图片Buffer下载
  • C#结合HALCON去除ROI选中效果的实现方法
  • django uwsgi启动报错failed to get the Python codec of the filesystem encoding
  • 如何永久删除三星手机中的照片?
  • Nestjs框架: 接口安全与响应脱敏实践 --- 从拦截器到自定义序列化装饰器
  • Charles中文版抓包工具功能解析,提升API调试与网络性能优化
  • Redis原理,命令,协议以及异步方式
  • 【数字投影】艺术视觉在展厅中的多维传达与设计创新
  • 【MySQL】初识索引
  • 51c视觉~合集16
  • 批量把在线网络JSON文件(URL)转换成Excel工具 JSON to Excel by WTSolutions
  • NOIP 2024 游记
  • 不同的子序列-二维动态规划
  • GeeLark 7月功能更新回顾
  • 【补题】Codeforces Round 776 (Div. 3) E. Rescheduling the Exam
  • 三方相机问题分析七:【datespace导致GPU异常】三方黑块和花图问题
  • 显示器同步技术终极之战:G-Sync VS. FreeSync
  • xml 格式化
  • 卷板矫平机:把“翘脾气”的金属板材变平整
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘huggingface_hub’问题
  • C# 装箱拆箱
  • 数据结构进阶 详谈红黑树
  • Redis(⑤-线程池隔离)
  • javaSE(基础):5.抽象类和接口