【C++】【常见面试题】最简版带大小和超时限制的LRU缓存实现

关键实现要点

  1. 数据结构选择

    • 双向链表:维护访问顺序
    • 哈希表:快速查找节点
  2. 淘汰策略

    • 容量满时删除最久未使用的节点
    • 访问时自动检查并删除超时节点
  3. 时间管理

    • 使用steady_clock记录节点时间戳
    • 访问时更新节点时间

完整代码实现

cpp 复制代码
#include <unordered_map>
#include <list>
#include <chrono>

using namespace std;
using namespace std::chrono;

class TimedLRUCache {
private:
    // 节点结构:存储键、值和访问时间
    struct Node {
        int key;
        int value;
        steady_clock::time_point timestamp;
        
        Node(int k, int v) : key(k), value(v), timestamp(steady_clock::now()) {}
    };
    
    int capacity_;                      // 缓存容量
    int timeout_ms_;                    // 超时时间(毫秒)
    list<Node> cache_list_;             // 双向链表,维护访问顺序
    unordered_map<int, decltype(cache_list_.begin())> key_map_;  // 哈希表,快速查找

public:
    // 构造函数:设置容量和超时时间
    TimedLRUCache(int capacity, int timeout_ms = 0) 
        : capacity_(capacity), timeout_ms_(timeout_ms) {}
    
    // 获取值,不存在返回-1
    int get(int key) {
        auto it = key_map_.find(key);
        if (it == key_map_.end()) return -1;
        
        // 检查是否超时
        if (isExpired(it->second)) {
            removeNode(it->second);
            return -1;
        }
        
        // 更新访问时间并移动到链表头部
        it->second->timestamp = steady_clock::now();
        cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
        return it->second->value;
    }
    
    // 插入或更新值
    void put(int key, int value) {
        auto it = key_map_.find(key);
        
        if (it != key_map_.end()) {
            // 键已存在,更新值和访问时间
            it->second->value = value;
            it->second->timestamp = steady_clock::now();
            cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
            return;
        }
        
        // 检查容量,删除最久未使用的节点
        if (cache_list_.size() >= capacity_) {
            removeLRUNode();
        }
        
        // 插入新节点到链表头部
        cache_list_.emplace_front(key, value);
        key_map_[key] = cache_list_.begin();
    }
    
    // 检查键是否存在且未超时
    bool contains(int key) {
        auto it = key_map_.find(key);
        if (it == key_map_.end()) return false;
        
        if (isExpired(it->second)) {
            removeNode(it->second);
            return false;
        }
        return true;
    }
    
    // 获取当前缓存大小
    int size() const {
        return cache_list_.size();
    }
    
    // 获取缓存容量
    int capacity() const {
        return capacity_;
    }

private:
    // 检查节点是否超时
    bool isExpired(decltype(cache_list_.begin()) iter) {
        if (timeout_ms_ == 0) return false; // 0表示永不过期
        
        auto now = steady_clock::now();
        auto elapsed = duration_cast<milliseconds>(now - iter->timestamp);
        return elapsed.count() > timeout_ms_;
    }
    
    // 删除指定节点
    void removeNode(decltype(cache_list_.begin()) iter) {
        key_map_.erase(iter->key);
        cache_list_.erase(iter);
    }
    
    // 删除最久未使用的节点
    void removeLRUNode() {
        if (cache_list_.empty()) return;
        
        // 从尾部开始查找未超时的节点删除
        for (auto it = cache_list_.rbegin(); it != cache_list_.rend(); ++it) {
            auto list_it = prev(it.base()); // 反向迭代器转正向迭代器
            if (!isExpired(list_it)) {
                removeNode(list_it);
                return;
            }
        }
        
        // 如果所有节点都超时,删除最后一个
        if (!cache_list_.empty()) {
            removeNode(prev(cache_list_.end()));
        }
    }
};

使用示例

cpp 复制代码
#include <iostream>
#include <thread>

int main() {
    // 创建缓存:容量3,超时时间1000ms
    TimedLRUCache cache(3, 1000);
    
    // 测试基本功能
    cache.put(1, 100);
    cache.put(2, 200);
    cache.put(3, 300);
    
    cout << "初始状态 - 大小: " << cache.size() << endl; // 3
    cout << "获取键2: " << cache.get(2) << endl;         // 200
    
    // 测试超时功能
    this_thread::sleep_for(milliseconds(1500));
    cout << "超时后获取键1: " << cache.get(1) << endl;   // -1 (已超时)
    cout << "超时后大小: " << cache.size() << endl;      // 2 (自动清理)
    
    // 测试LRU淘汰
    cache.put(4, 400);
    cache.put(5, 500);
    cout << "插入新键后大小: " << cache.size() << endl;  // 3
    
    return 0;
}

关键点梳理

1. 数据结构设计

  • 双向链表:维护节点的访问顺序,最近访问的在头部
  • 哈希表:提供O(1)时间复杂度的节点查找
  • 节点结构:包含键、值和时间戳三要素

2. 淘汰策略实现

cpp 复制代码
// 关键代码:删除最久未使用节点
void removeLRUNode() {
    // 从尾部开始查找(最久未使用)
    for (auto it = cache_list_.rbegin(); it != cache_list_.rend(); ++it) {
        auto list_it = prev(it.base());
        if (!isExpired(list_it)) {  // 找到第一个未超时的节点
            removeNode(list_it);
            return;
        }
    }
    // 所有节点都超时,删除最后一个
    if (!cache_list_.empty()) {
        removeNode(prev(cache_list_.end()));
    }
}

3. 时间管理机制

cpp 复制代码
// 关键代码:超时检查
bool isExpired(decltype(cache_list_.begin()) iter) {
    if (timeout_ms_ == 0) return false; // 永不过期
    
    auto now = steady_clock::now();
    auto elapsed = duration_cast<milliseconds>(now - iter->timestamp);
    return elapsed.count() > timeout_ms_;
}

4. 访问顺序维护

cpp 复制代码
// 关键代码:节点访问时的处理
it->second->timestamp = steady_clock::now();  // 更新时间
cache_list_.splice(cache_list_.begin(), cache_list_, it->second); // 移动到头部

核心优势

  1. 时间复杂度:get和put操作都是O(1)
  2. 空间复杂度:O(capacity),只存储有限节点
  3. 自动清理:访问时自动检查并删除超时节点
  4. 线程安全:单线程环境下安全使用

这个实现平衡了功能完整性和代码简洁性,适合需要基础缓存功能的场景。

相关推荐
无限进步_17 分钟前
【C++】大数相加算法详解:从字符串加法到内存布局的思考
开发语言·c++·windows·git·算法·github·visual studio
C+-C资深大佬34 分钟前
C++ 数据类型转换是如何实现的?
开发语言·c++·算法
Mr__Miss1 小时前
JAVA面试-框架篇
java·spring·面试
Python算法实战1 小时前
《大模型面试宝典》(2026版) 正式发布!
人工智能·深度学习·算法·面试·职场和发展·大模型
a努力。2 小时前
2026 AI 编程终极套装:Claude Code + Codex + Gemini CLI + Antigravity,四位一体实战指南!
java·开发语言·人工智能·分布式·python·面试
oioihoii2 小时前
回归测试:软件演进中的质量守护神与实践全指南
c++
十五年专注C++开发3 小时前
CMake基础: 在release模式下生成调试信息的方法
linux·c++·windows·cmake·跨平台构建
点云SLAM3 小时前
C++(C++17/20)最佳工厂写法和SLAM应用综合示例
开发语言·c++·设计模式·c++实战·注册工厂模式·c++大工程系统
Q741_1473 小时前
C++ 队列 宽度优先搜索 BFS 力扣 662. 二叉树最大宽度 每日一题
c++·算法·leetcode·bfs·宽度优先
csdn_aspnet3 小时前
C++跨平台开发:工程难题与解决方案深度解析
c++