146.LRU Cache

Solve this problem incrementally, don't try to get to the optimal solution in one shot.
Think out loud and achieve V0, reflect what can be optimized.

Thought process:
What data structure to use to:

  1. keep track of key/value pairs
  2. keep track of order of being recently accessed

It's fairly obvious 1 needs a map.
for 2, the initial thoughts are it needs to be a list like structure, where one element can be added to the end (last recently accessed), and can remove the oldest
item from the head. This suggests access to the head and the tail of the data structure is needed. They need to be done in O(1), which is THE most important hint that it has to be a doubly linked list (supports head and tail, moving an element to the end takes only O(1).

That is the most important step, to figure out what data structure to use. Second important thing is when moving element, we need to make sure the prev and next nodes are correctly updated. The mental model here is to imagine cutting a link out of a chain - you need to reconnect both sides.

From V0, we want to separate the linked like operations to helper functions: removeNode, addToTail, moveToTail:
V1:

class LRUCache {
    public:
        class Node {
         public:
            Node* prev;
            Node* next;
            int key;
            int value;
    
            Node(int key, int value) 
            {
                this->key = key;
                this->value = value;
                prev = nullptr;
                next = nullptr;
            }
        };
    
        std::unordered_map<int, Node*> keyValues;
        int capacity_;
        Node* head_; 
        Node* tail_;
    
    
        LRUCache(int capacity) : capacity_(capacity), head_(nullptr), tail_(nullptr) {}
    
     
        int get(int key) {
            auto it = keyValues.find(key);
            if (it != keyValues.end()){
                auto cur = it->second; 
                if (cur == tail_) {
                    return cur->value;
                }
                // Remove cur from current position - handle both directions
                removeNode(cur); 
                // move cur to the end
                addToTail(cur);
                return cur->value;
            } 
            return -1;
        }
        
        void put(int key, int value) {
            auto it = keyValues.find(key);
            if (it != keyValues.end()){
                it->second->value = value;
                get(key);
                return;
            }
            
            // If adding new node, needs to check if map is at capactiy
            if (keyValues.size() >= capacity_){
                Node* oldHead = head_;
                keyValues.erase(oldHead->key);
                //evict the head
                removeHead();
            }
    
            Node* newNode = new Node(key, value);
            keyValues[key] = newNode;
            
            // Update key order
            newNode->prev = tail_;
            if (tail_ != nullptr){
                tail_->next = newNode;
            } else {
                head_ = newNode;
            }
            tail_ = newNode;
        }


       
        void addToTail(Node* node){
            if (tail_ != nullptr){
                tail_->next = node;
                node->prev = tail_;
            }
            node->next = nullptr;
            tail_ = node;
        }

        void removeNode(Node* node){
            if (node->prev != nullptr){
                node->prev->next = node->next; // Bridge the gap
            } else {
                head_ = node->next;  // cur was head, update head
            }

            if (node->next != nullptr){
                node->next->prev = node->prev; // Bridge the gap backwards
            }
        }


        void removeHead(){
            head_ = head_->next;
            if (head_ != nullptr){
                head_->prev = nullptr;
            } else {
                tail_ = nullptr;
            }
        }

    };

V0:

Pay attention to handling both prev and next when moving nodes.

#include <unordered_map>
#include <iostream>

class LRUCache {
    public:
        class Node {
         public:
            Node* prev;
            Node* next;
            int key;
            int value;
    
            Node(int key, int value) 
            {
                this->key = key;
                this->value = value;
                prev = nullptr;
                next = nullptr;
            }
        };
    
        std::unordered_map<int, Node*> keyValues;
        int capacity_;
        Node* head_; 
        Node* tail_;
    
    
        LRUCache(int capacity) : capacity_(capacity), head_(nullptr), tail_(nullptr) {}
    
     
        int get(int key) {
            auto it = keyValues.find(key);
            if (it != keyValues.end()){
                auto cur = it->second; 
                if (cur == tail_) {
                    return cur->value;
                }
                // Remove cur from current position - handle both directions
                if (cur->prev != nullptr){
                    cur->prev->next = cur->next;
                } else {
                    head_ = cur->next;
                }
                
                if (cur->next != nullptr){      // handle next pointer
                    cur->next->prev = cur->prev;
                }  
                 
                // move cur to the end
                cur->prev = tail_;
                cur->next = nullptr;
    
                if (tail_ != nullptr){
                    tail_->next = cur;     
                } else {
                    head_ = cur;
                }
                tail_ = cur;
                return cur->value;
            } 
            return -1;
        }
        
        void put(int key, int value) {
            auto it = keyValues.find(key);
            if (it != keyValues.end()){
                it->second->value = value;
                get(key);
                return;
            }
            
            // If adding new node, needs to check if map is at capactiy
            if (keyValues.size() >= capacity_){
                Node* oldHead = head_;
                keyValues.erase(oldHead->key);
                //evict the head
                head_ = head_->next;
                if (head_ != nullptr) {
                    head_->prev = nullptr;
                } else {
                    tail_ = nullptr;
                }
            }
    
            Node* newNode = new Node(key, value);
            keyValues[key] = newNode;
            
            // Update key order
            newNode->prev = tail_;
            if (tail_ != nullptr){
                tail_->next = newNode;
            } else {
                head_ = newNode;
            }
            tail_ = newNode;
        }
    };
   
    
    /**
     * 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);
     */



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 8,519评论 0 5
  • 为了让我有一个更快速、更精彩、更辉煌的成长,我将开始这段刻骨铭心的自我蜕变之旅!从今天开始,我将每天坚持阅...
    李薇帆阅读 5,941评论 0 3
  • 似乎最近一直都在路上,每次出来走的时候感受都会很不一样。 1、感恩一直遇到好心人,很幸运。在路上总是...
    时间里的花Lily阅读 5,211评论 0 2
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 3,541评论 0 1
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 3,508评论 0 0