• lettcode 上的几道哈希表与链表组合的数据结构题


    lettcode 上的几道哈希表与链表组合的数据结构题

    下面这几道题都要求在O(1)时间内完成每种操作。

    LRU缓存

    LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

    做法:

    • 使用先进先出的队列,队尾的元素即是可能要淘汰的。

    • 由于需要查找某个key在队列中的位置,需要一种数据结构快速定位,并且快速删除。

    • 使用链表来实现队列的功能,同时使用哈希表记录每个key对应的链表结点。

    • 可以手写一个双向哈希链表,也可以使用c++ std库中的list+unordered_map来代替。

    typedef struct Node
    {
        int key;
        int value;
        Node *prev;
        Node *next;
        Node(int k, int v):key(k), value(v), prev(NULL), next(NULL){}
    }Node;
    class HashDoubleLinkList
    {
    private:
        int size;
        Node *head;
        Node *tail;
        unordered_map<int, Node*> key_dict;
    
    public:
        void init(){
            key_dict.clear();
            size = 0;
            head = newNode(0,0);
            tail = newNode(0,0);
            head->next = tail;
            tail->prev = head;
        }
        int getsize() { return size; }
        Node *newNode(int k,int v){
            return new Node(k, v);
        } 
        //find node by key, if key not exist, return NULL
        Node *find(int key){
            auto it = key_dict.find(key);
            if(it == key_dict.end()) return NULL;
            return it->second;
        }
        //remove node by key, if key not exist, return NULL
        Node* removekey(int key){
            Node *node = find(key);
            if(!NULL) return NULL;
            remove(node);
            return node;
        }
        //remove node 
        void remove(Node *node){
            node->prev->next = node->next;
            node->next->prev = node->prev;
            key_dict.erase(node->key);
            size--;
        }
        //pop the last element in list
        Node* pop_back(){
            if(size <= 0) return NULL;
            size--;
            Node *node = tail->prev;
            node->prev->next = tail;
            tail->prev = node->prev;
            key_dict.erase(node->key);
            return node; 
        }
        //pop the first element in list
        Node* pop_front(){
            if(size <= 0) return NULL;
            size--;
            Node *node = head->next;
            head->next = node->next;
            node->next->prev = head; 
            key_dict.erase(node->key);
            return node;
        }
        //insert before first element in list
        void push_front(Node *node){
            head->next->prev = node;
            node->next = head->next;
            head->next = node;
            node->prev = head;
            size++;
            key_dict[node->key] = node;
        }
        //insert after last element in list
        void push_back(Node *node){
            node->next = tail;
            node->prev = tail->prev;
            tail->prev->next = node;
            tail->prev = node;
            size++;
            key_dict[node->key] = node;
        }
    };
    class LRUCache {
        int cap;
        HashDoubleLinkList dl;
    public:
        LRUCache(int capacity) {
            cap = capacity;
            dl.init();
        }
        
        int get(int key) {
            Node *node = dl.find(key);
            if(node == NULL) return -1;
            dl.remove(node);
            dl.push_front(node);
            return node->value;
        }
        
        void put(int key, int value) {
            Node *node = dl.find(key);
            if(node == NULL){
                if(dl.getsize() == cap){
                    dl.pop_back();
                }
                dl.push_front(new Node(key, value));
            }else{
                dl.remove(node);
                node->value = value;
                dl.push_front(node);
            }
        }
    };
    

    LFU缓存

    LFU是Least Frequency Used的缩写,即最少使用次数,次数相等时即等同于LRU缓存。每次选择访问次数最少的页面予以淘汰,若存在多个次数最少的页面,选择访问时间最远的页面淘汰。

    做法:

    • 将访问次数相同的元素丢到一个链表中,这个链表的操作就跟LRU缓存一样了。
    • 用哈希表记录次数对应的链表, key对应的链表结点,和key对应的访问次数和值。

    使用了三个哈希表+一个链表,LFU缓存比较耗费内存。

    class LFUCache {
        int cap;
        int minFreq;
        unordered_map<int, list<int> > FreqKey;
        unordered_map<int, pair<int,int> > KeyFreqAndValue;
        unordered_map<int, list<int>::iterator> FreqKeyIter;
    private:
    public:
        LFUCache(int capacity) {
            cap = capacity;
            minFreq = 1;
        }
        int get(int key) {
            auto fv = KeyFreqAndValue.find(key);
            if(fv == KeyFreqAndValue.end()) return -1;
            FreqKey[fv->second.first].erase(FreqKeyIter[key]);
            fv->second.first++;
            if (FreqKey.find(fv->second.first) == FreqKey.end()){
                FreqKey[fv->second.first] = list<int>();
            }
            FreqKey[fv->second.first].push_front(key);
            FreqKeyIter[key] = FreqKey[fv->second.first].begin();
            if(FreqKey[minFreq].empty()) minFreq++;
            return fv->second.second;
        }
        
        void put(int key, int value) {
            if(cap <= 0) return ;
            if(get(key) != -1){
                KeyFreqAndValue[key].second = value;
                return ;
            }
            if(KeyFreqAndValue.size() == cap){
                int pop_key = *FreqKey[minFreq].rbegin();
                KeyFreqAndValue.erase(pop_key);
                FreqKeyIter.erase(pop_key);
                FreqKey[minFreq].pop_back();
            }
            minFreq = 1;
            FreqKey[1].push_front(key);
            KeyFreqAndValue[key] = make_pair(1, value);
            FreqKeyIter[key] = FreqKey[1].begin();
        }
    };
    

    全O(1)的数据结构

    这道题有四种操作

    • Inc(key) - 插入一个新的值为 1 的 key。或者使一个存在的 key 增加一,保证 key 不为空字符串
    • Dec(key) - 如果这个 key 的值是 1,那么把他从数据结构中移除掉。否者使一个存在的 key 值减一。如果这个 key 不存在,这个函数不做任何事情。key 保证不为空字符串。
    • GetMaxKey() - 返回 key 中值最大的任意一个。如果没有元素存在,返回一个空字符串""。
    • GetMinKey() - 返回 key 中值最小的任意一个。如果没有元素存在,返回一个空字符串""。

    做法:

    • 这里的O(1)应该是不包括计算key的哈希值的时间的。
    • 双向链表将值从小到大串连起来, 链表中每个结点维护一个哈希表存储所有值相同的key,
    • 维护一个哈希表记录值在链表中对应的结点,一个哈希表记录key对应的值
    • 求值最大/最小的key就是求链表的首尾即可。
    class AllOne {
        unordered_map<string, int> values;
        unordered_map<int, list<unordered_set<string>>::iterator> count_iter;
        list<unordered_set<string> > count_keys;
    public:
        /** Initialize your data structure here. */
        AllOne() {
            values.clear();
            count_iter.clear();
            count_keys.clear();
        }
        void remove(list<unordered_set<string>>::iterator iter, string key, int value){
                    (*iter).erase(key);
                    if((*iter).empty()){
                        count_iter.erase(value);
                        count_keys.erase(iter);
                    }
        } 
        /** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */
        void inc(string key) {
            auto it = values.find(key);
            if(it == values.end()){
                values[key] = 1;
                if(count_iter.find(1) == count_iter.end()){
                    count_keys.push_front({key});
                    count_iter[1] = count_keys.begin();
                }else{
                    (*count_iter[1]).insert(key);
                }
            }else{
                int value = it->second;
                //update values
                it->second++;
    
                //update value + 1
                auto iter = count_iter[value];
                iter++;
                if (count_iter.find(value + 1) == count_iter.end()){
                    count_iter[value + 1] = count_keys.insert(iter, {key});
                }else{
                    (*iter).insert(key);
                }
                //delte value
                remove(count_iter[value], key, value);
            }
        }
        
        /** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */
        void dec(string key) {
            auto it = values.find(key);
            if(it == values.end()) return ;
            int value = it->second;
            //update values
            it->second--;
            //update value - 1
            auto iter = count_iter[value];
            if(it->second == 0){
                values.erase(key);
            }else{
                if (count_iter.find(value - 1) == count_iter.end()){
                    count_iter[value - 1] = count_keys.insert(iter, {key});
                }else{
                    (*count_iter[value - 1]).insert(key);
                }
            }
            remove(iter, key, value);
        }
        
        /** Returns one of the keys with maximal value. */
        string getMaxKey() {
            if(count_keys.empty()) return "";
           return *(count_keys.back().begin());
        }
        
        /** Returns one of the keys with Minimal value. */
        string getMinKey() {
            if(count_keys.empty()) return "";
            return *(count_keys.front().begin());
        }
    };
    
    /**
     * Your AllOne object will be instantiated and called as such:
     * AllOne* obj = new AllOne();
     * obj->inc(key);
     * obj->dec(key);
     * string param_3 = obj->getMaxKey();
     * string param_4 = obj->getMinKey();
     */
    
  • 相关阅读:
    canvas性能优化——离屏渲染
    event.target 和 event.currentTarget 的区别
    Electron 主进程和渲染进程互相通信
    谈谈 JS 垃圾回收机制
    【Vue】Vue中render函数用到的with(this)中with的用法及其优缺点
    Java递归读取文件路径下所有文件名称并保存为Txt文档
    Java读取Excel指定列的数据详细教程和注意事项
    Sybase ASE无响应的又一个情况
    AWR报告导出的过程报ORA-06550异常
    如何借助浏览器Console使用Js进行定位和操作元素
  • 原文地址:https://www.cnblogs.com/jiachinzhao/p/11474085.html
Copyright © 2020-2023  润新知