leetcode_146 LRU缓存

1. 题意

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存

int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。

void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

2. 题解

单次插入需要 O ( 1 ) O(1) O(1),

查找也需要是 O ( 1 ) O(1) O(1)。

因此我们需要哈希表和双向链表来完成这一操作。

2.1 我的解

直接自己写个双向链表。

同时我们需要维护链表头和尾。

需要注意的是一些异常情况,比如链表空,或者只有一个头节点。

cpp 复制代码
class LRUCache {


public:
    struct LRUNode {
        LRUNode(int k, int v):key(k),value(v) {

        }
        int key;
        int value;
    };

    struct LRUList {
        LRUList *pre;
        LRUList *next;
        LRUNode *node;
    };

    LRUCache(int capacity):max_cap_(capacity){
        
    }
    
    int get(int key) {
        // cout << "get " << key << "\n";
        if ( hs.count(key) ) {
            LRUList *cur = hs[key];
            if ( cur != head) {
                if ( cur == tail) 
                    tail = cur->pre;
            
                cur->pre->next = cur->next;
                if (cur->next)
                    cur->next->pre = cur->pre;
                
                head->pre  = cur;
                cur->next = head;
                cur->pre  = NULL;
                head = cur;                
            }
            return cur->node->value;
        }
        return -1;
    }
    void put(int key, int value) {
        // cout << "put: [ " << key <<", " << value << " ]" << "\n"; 
        if ( hs.count(key) ) {
            LRUList *cur = hs[key];

            cur->node->value = value;

            if (cur == head)
                return;

            if ( cur == tail) {
                tail = cur->pre;
            }

            cur->pre->next = cur->next;
            if (cur->next)
                cur->next->pre = cur->pre;
            
            head->pre  = cur;
            cur->next = head;
            cur->pre  = NULL;
            head = cur;
        }
        else {
            LRUList *cur = new LRUList;
            cur->pre = cur->next = NULL;
            cur->node = new LRUNode(key, value);
            ++cur_cap_;

            hs[key] = cur;

            if (head)
                head->pre = cur;
            if (tail == NULL)
                tail = cur;

            cur->next = head;
            head = cur;

            if ( cur_cap_ > max_cap_) {
                LRUList *del = tail;

                // cout << "del " << del->node->key << "\n";

                hs.erase(del->node->key);

                tail = del->pre;
                if (tail)
                    tail->next = NULL;
                del->pre = NULL;
                del->next = NULL;
                delete del->node;
                delete del;
                --cur_cap_;
            }
        }   
    }
private:
    int max_cap_;
    int cur_cap_{};

    unordered_map<int,LRUList *> hs;
    LRUList *head{};
    LRUList *tail{};
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */
2.2 0x3f的解

我看0x3f的解,主要是模块化,还有加了一个哨兵节点就省去了首尾节点的判断。

cpp 复制代码
class LRUCache {
public:
struct LRUNode {

        LRUNode():pre(NULL),next(NULL) {

        }
        LRUNode(int key, int val):k(key),v(val), pre(NULL), next(NULL){
            
        }
        int k;
        int v;
        LRUNode *pre;
        LRUNode *next;
    };
private:
    int cap_;

    unordered_map<int, LRUNode *> key_to_node;
    LRUNode *dumNode;
    
public:

    

    LRUCache(int capacity) : cap_(capacity), dumNode(new LRUNode()) {
        dumNode->pre = dumNode;
        dumNode->next = dumNode;
    }

    void remove(LRUNode *cur) {
        cur->pre->next = cur->next;
        cur->next->pre = cur->pre;
        cur->pre = cur->next = NULL;
    }

    void push_front(LRUNode *cur) { 

        cur->next = dumNode->next;
        cur->pre  = dumNode;

        dumNode->next->pre =cur;
        dumNode->next = cur;
    }

    int get(int key) {
        //cout << "get " << key << "\n";
        auto it = key_to_node.find(key);
        if ( it != key_to_node.end()) {
            LRUNode *cur = it->second;

            remove( cur );
            push_front( cur );

            return cur->v;
        }

        return -1;
    }

    void put(int key, int value) {
        //cout << "put [ " << key << ", " << value << " ]\n";

        auto it = key_to_node.find(key);
        if ( it != key_to_node.end()) {
            LRUNode *cur  = it->second;
            cur->v = value;

            remove( cur );
            push_front( cur );
        }
        else {
            LRUNode *cur = new LRUNode(key, value);
            key_to_node[key] = cur;
            push_front(cur);

            if ( key_to_node.size() > cap_) {
                LRUNode *del_node = dumNode->pre;
                key_to_node.erase( del_node->k);
                remove( del_node);
                delete del_node;
            }
        }
    }
};

还有一种就是使用标准库的双向链表了,不过标准库的api真的感觉好难用!

cpp 复制代码
class LRUCache {


public:
    struct LRUNode {
        LRUNode(int k, int v):key(k),value(v) {

        }
        int key;
        int value;
    };

    struct LRUList {
        LRUList *pre;
        LRUList *next;
        LRUNode *node;
    };

    LRUCache(int capacity):max_cap_(capacity){
        
    }

    int get(int key) {
        // cout << "get " << key << "\n";
        auto it = key_to_it.find(key);
        if ( it  == key_to_it.end()) {
            return -1;
        }
        
        cached_list.splice( cached_list.begin(), cached_list, it->second);

        return cached_list.begin()->value;
    }
    void put(int key, int value) {
        
        auto it = key_to_it.find( key );
        if ( it != key_to_it.end()) {
            it->second->value = value;

            cached_list.splice( cached_list.begin(), cached_list, it->second);
        }
        else {
            auto nNode = new LRUNode(key, value);
            cached_list.push_front( *nNode );
            key_to_it[key] = cached_list.begin();

            if ( cached_list.size() > max_cap_) {
                key_to_it.erase(cached_list.back().key );
                cached_list.pop_back();
            }
        }
    }
private:
    int max_cap_;
    unordered_map<int, list<LRUNode>::iterator> key_to_it;

    list<LRUNode> cached_list;
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

3. 参考

0x3f

cpp-splice

相关推荐
董董灿是个攻城狮9 小时前
AI视觉连载8:传统 CV 之边缘检测
算法
AI软著研究员16 小时前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish16 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
颜酱17 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
地平线开发者1 天前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮1 天前
大模型连载2:初步认识 tokenizer 的过程
算法
地平线开发者1 天前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考1 天前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习
HXhlx2 天前
CART决策树基本原理
算法·机器学习
Wect2 天前
LeetCode 210. 课程表 II 题解:Kahn算法+DFS 双解法精讲
前端·算法·typescript