数据结构与算法面试题:实现一个 LRU 缓存,支持如下操作:获取值、更新值、删除键值对和插入键值对

简介: 数据结构与算法面试题:实现一个 LRU 缓存,支持如下操作:获取值、更新值、删除键值对和插入键值对

数据结构与算法面试题:实现一个 LRU 缓存,支持如下操作:获取值、更新值、删除键值对和插入键值对

简介:实现一个 LRU 缓存,支持如下操作:获取值、更新值、删除键值对和插入键值对

算法思路

  1. 使用一个双向链表存储每个键值对,按照访问时间从早到晚依次排列,越晚访问的节点越靠近双向链表的头部。这里使用了 C++ 中的 list 模板类。
  2. 使用一个哈希表存储键和对应的节点指针,可以用 C++ 标准库中的 unordered_map 实现。
  3. 对于插入、更新、删除操作需要同时修改双向链表和哈希表。
  4. 当缓存已满时,在插入新的键值对之前,需要将最近最少使用的节点从双向链表中删除,并从哈希表中删除相应的键值对。
  • c++
#include <iostream>
#include <unordered_map>
#include <list>
using namespace std;
class LRUCache {
public:
    LRUCache(int capacity) { // 构造函数
        cap = capacity; // 缓存容量
    }
    int get(int key) { // 获取值操作
        if (cache.count(key)) { // 缓存命中
            auto it = cache[key]; // 从哈希表中找到对应的迭代器
            int val = it->second; // 取出键值对中的值
            recent.erase(it); // 将访问过的键位于链表头部,将其删除
            recent.push_front(key); // 将当前键放在链表头部
            cache[key] = recent.begin(); // 更新键在双向链表中的对应迭代器位置
            return val;
        }
        return -1; // 缓存未命中
    }
    void put(int key, int value) { // 更新值操作
        if (cache.count(key)) { // 若键已存在,则替换其值
            auto it = cache[key]; // 从哈希表中找到对应的迭代器
            recent.erase(it); // 将访问过的键位于链表头部,将其删除
        }
        else if (recent.size() == cap) { // 若链表已满,则删除最久未使用的键
            int old_key = recent.back(); // 查找链表尾部的键值并保留
            recent.pop_back(); // 删除链表尾部的键值对
            cache.erase(old_key); // 从哈希表中删除对应的键
        }
        recent.push_front(key); // 将当前键放在链表头部
        cache[key] = recent.begin(); // 更新键在双向链表中的对应迭代器位置
        cache[key]->second = value; // 更新键值对中的值
    }
private:
    int cap;
    unordered_map<int, list<int>::iterator> cache; // 哈希表,键为键值,值为双向链表中对应节点的迭代器
    list<int> recent; // 双向链表,存储键值
};
int main() {
    LRUCache lru_cache(2);
    lru_cache.put(1, 1);
    lru_cache.put(2, 2);
    cout << lru_cache.get(1) << endl; // 查询键1,返回1,并将该键移动到链表头部
    lru_cache.put(3, 3); // 插入键3,此时容量超出限制,删除最近最少使用的键2
    cout << lru_cache.get(2) << endl; // 查询键2,未命中,返回-1
    lru_cache.put(4, 4); // 插入键4,此时容量超出限制,删除最近最少使用的键1
    cout << lru_cache.get(1) << endl; // 查询键1,未命中,返回-1
    cout << lru_cache.get(3) << endl; // 查询键3,返回3,并将该键移动到链表头部
    cout << lru_cache.get(4) << endl; // 查询键4,返回4,并将该键移动到链表头部
    return 0;
}
  • java
import java.util.*;
class LRUCache {
    private int cap;
    private Map<Integer, Integer> cache; // 哈希表,键为键值,值为对应的值
    private Deque<Integer> recent; // 双向链表,存储键值
    public LRUCache(int capacity) { // 构造函数
        cap = capacity; // 缓存容量
        cache = new HashMap<>();
        recent = new LinkedList<>();
    }
    public int get(int key) { // 获取值操作
        if (cache.containsKey(key)) { // 缓存命中
            recent.remove(key); // 移除双向链表中的键
            recent.addFirst(key); // 将当前键放在链表头部
            return cache.get(key); // 返回值
        }
        return -1; // 缓存未命中
    }
    public void put(int key, int value) { // 更新值操作
        if (cache.containsKey(key)) { // 若键已存在,则替换其值
            recent.remove(key); // 移除双向链表中的键
        }
        else if (recent.size() == cap) { // 若链表已满,则删除最久未使用的键
            int old_key = recent.removeLast(); // 查找链表尾部的键值并保留
            cache.remove(old_key); // 从哈希表中删除对应的键值对
        }
        recent.addFirst(key); // 将当前键放在链表头部
        cache.put(key, value); // 更新键值对中的值
    }
}
public class Main {
    public static void main(String[] args) {
        LRUCache lru_cache = new LRUCache(2);
        lru_cache.put(1, 1);
        lru_cache.put(2, 2);
        System.out.println(lru_cache.get(1)); // 查询键1,返回1,并将该键移动到链表头部
        lru_cache.put(3, 3); // 插入键3,此时容量超出限制,删除最近最少使用的键2
        System.out.println(lru_cache.get(2)); // 查询键2,未命中,返回-1
        lru_cache.put(4, 4); // 插入键4,此时容量超出限制,删除最近最少使用的键1
        System.out.println(lru_cache.get(1)); // 查询键1,未命中,返回-1
        System.out.println(lru_cache.get(3)); // 查询键3,返回3,并将该键移动到链表头部
        System.out.println(lru_cache.get(4)); // 查询键4,返回4,并将该键移动到链表头部
    }
}


相关文章
|
2月前
|
缓存 算法 数据挖掘
深入理解缓存更新策略:从LRU到LFU
【10月更文挑战第7天】 在本文中,我们将探讨计算机系统中缓存机制的核心——缓存更新策略。缓存是提高数据检索速度的关键技术之一,无论是在硬件还是软件层面都扮演着重要角色。我们会详细介绍最常用的两种缓存算法:最近最少使用(LRU)和最少使用频率(LFU),并讨论它们的优缺点及适用场景。通过对比分析,旨在帮助读者更好地理解如何选择和实现适合自己需求的缓存策略,从而优化系统性能。
64 3
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
SQL 缓存 关系型数据库
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴因未能系统梳理MySQL缓存机制而在美团面试中失利。为此,尼恩对MySQL的缓存机制进行了系统化梳理,包括一级缓存(InnoDB缓存)和二级缓存(查询缓存)。同时,他还将这些知识点整理进《尼恩Java面试宝典PDF》V175版本,帮助大家提升技术水平,顺利通过面试。更多技术资料请关注公号【技术自由圈】。
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
|
2月前
|
算法 前端开发 Java
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
这篇文章总结了单链表的常见面试题,并提供了详细的问题分析、思路分析以及Java代码实现,包括求单链表中有效节点的个数、查找单链表中的倒数第k个节点、单链表的反转以及从尾到头打印单链表等题目。
36 1
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
|
2月前
|
缓存 监控 算法
小米面试题:多级缓存一致性问题怎么解决
【10月更文挑战第23天】在现代分布式系统中,多级缓存架构因其能够显著提高系统性能和响应速度而被广泛应用。
60 3
|
2月前
|
存储 缓存 NoSQL
阿里面试题:缓存的一些常见的坑,你遇到过哪些,怎么解决的?
阿里面试题:缓存的一些常见的坑,你遇到过哪些,怎么解决的?
|
2月前
|
缓存 分布式计算 NoSQL
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
81 2
|
3月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
77 8
|
4月前
|
缓存 算法 前端开发
深入理解缓存淘汰策略:LRU和LFU算法的解析与应用
【8月更文挑战第25天】在计算机科学领域,高效管理资源对于提升系统性能至关重要。内存缓存作为一种加速数据读取的有效方法,其管理策略直接影响整体性能。本文重点介绍两种常用的缓存淘汰算法:LRU(最近最少使用)和LFU(最不经常使用)。LRU算法依据数据最近是否被访问来进行淘汰决策;而LFU算法则根据数据的访问频率做出判断。这两种算法各有特点,适用于不同的应用场景。通过深入分析这两种算法的原理、实现方式及适用场景,本文旨在帮助开发者更好地理解缓存管理机制,从而在实际应用中作出更合理的选择,有效提升系统性能和用户体验。
221 1
|
4月前
|
存储 缓存 Java