[LeetCode]146. LRU Cache

双向链表,LRU的规则

Posted by JinFei on February 6, 2020

题目描述

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
The cache is initialized with a positive capacity.

Follow up:

Could you do both operations in O(1) time complexity?

Example1:

LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4

解题思路

  • LRU Cache,感觉这里最难的应该是数据结构需要自己定义
  • 首先定义LinkNode双向链表的结构(包含,前指针prev,下一个指针next,具体的key,value,构造函数)
  • 然后LRU Cache里,私有的数据结构有,容量大小,此时的size(便于要不要扩容),一个基于Hashmap的数据结构,key为int型,value为上面定义的LinkNode,还有指向LinkNode的头尾指针
  • LRU Cache里,构造函数有会初始化头尾指针,让其互相指向,容量大小等等
  • put的时候,要建立映射关系 mmap[key] = value

C++代码

struct LinkNode{
    LinkNode* prev = NULL;
    LinkNode* next = NULL;
    int key;
    int value;
    LinkNode() = default;
    LinkNode(int key, int value) : key(key), value(value), prev(NULL), next(NULL){}
};

class LRUCache {
private:
    int capacity = 0;
    int size = 0;
    unordered_map<int, LinkNode*> mmap;
    LinkNode* head = NULL;
    LinkNode* tail = NULL;
public:
    LRUCache(int capacity) {
        this -> capacity = capacity;
        head = new LinkNode;
        tail = new LinkNode;
        head -> next = tail;
        tail -> next = head;        // double LinkNOde
    }
    
    int get(int key) {
        if(mmap.find(key) != mmap.end()){
            LinkNode* node = mmap.at(key);
            node -> next -> prev = node -> prev;
            node -> prev -> next = node -> next;
            node -> next = head -> next;
            head -> next -> prev = node;
            head -> next = node;
            node -> prev = head;
            return node -> value;
            
        }else{
            return -1;
        }
    }
    
    void put(int key, int value) {
        if(get(key) != -1){
            head -> next -> value = value;  // update
            return;
        }
        if(mmap.count(key) == 0){
            ++size;
        }
        if(size > capacity){
            LinkNode* destroy = tail -> prev;
            tail -> prev = destroy -> prev;
            destroy -> prev -> next = tail;
            mmap.erase(destroy -> key);
            delete destroy;
            --size;
        }
        LinkNode* node = new LinkNode(key, value);
        node -> next = head -> next;
        head -> next -> prev = node;
        head -> next = node;
        node -> prev = head;
        mmap[key] = node;       
        
    }
};

/**
 * 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);
 */

0206解题思路

  • 首先容易想到这应该是个双向链表
  • 那么双向链表都该有哪些数据成员呢? 至少指向下一个,前一个指针应该是有的,然后代表key,value的数值应该是有的
  • 其次是LRU类,应该有哪些数据成员?
    1. 首先,size是需要的,(如果size超过最大容量的话,需要从末尾删除最冷的元素),
    2. capacity也是需要的,put的时候,需要判断是否size与之相等,达到最大元素,
    3. 双向链表的head, tail也是需要的,
    4. 承载数据的哈希结构也是需要的,这里用 unordered_map<key, LinkNode*>
  • 构造函数应该怎么初始化?
    1. 初始化capacity
    2. new节点,将head,tail相互指向(即head -> next = tail, tail -> next = head) // 这样用指针就互相指向了
  • get
    1. mmap进行查找,如果能够找到,用at方法定位到具体位置,需要把找到的元素进行移动到最前面 head 指针指向的位置
    2. 移动需要两点,需要先断开指针,然后移动到最前面
    3. 如果没有找到,返回-1即可
  • put
    1. 关键是要将key,value插入
    2. 插入分两种情况,存在和不存在,如果存在,那么get(key)方法已经将元素移动到最前面了, 这时候进行更新就可
    3. 如果不存在,那么需要重新插入一个节点。在插入之前,需要进行判断,size与capacity的大小,如果当前size大于capacity,则还需要进行删除操作,淘汰末尾节点,也是断开指针,然后delete元素,需要将mmap按照key进行删除,即mmap.erase(key)
    4. 下面就开始重新插入一个节点了
    5. 插入完节点后,别忘了在mmap哈希表中进行赋值操作。 mmap[key] = node // node 为新生成的节点