【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. 线程安全:单线程环境下安全使用

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

相关推荐
陌路208 小时前
C23构造函数与析构函数
开发语言·c++
_OP_CHEN8 小时前
C++进阶:(二)多态的深度解析
开发语言·c++·多态·抽象类·虚函数·多态的底层原理·多态面试题
金色熊族10 小时前
装饰器模式(c++版)
开发语言·c++·设计模式·装饰器模式
Dream it possible!10 小时前
LeetCode 面试经典 150_链表_旋转链表(64_61_C++_中等)
c++·leetcode·链表·面试
阿健君11 小时前
Android 高频八股文十问
面试
T___T12 小时前
AIGC 实战:用 pnpm 提速 + Node.js 调用 OpenAI 🤖
面试·node.js
CS创新实验室13 小时前
典型算法题解:长度最小的子数组
数据结构·c++·算法·考研408
我有一些感想……13 小时前
浅谈 BSGS(Baby-Step Giant-Step 大步小步)算法
c++·算法·数论·离散对数·bsgs
j_xxx404_13 小时前
C++ STL:string类(3)|operations|string类模拟实现|附源码
开发语言·c++