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

Posted jiachinzhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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();
 */

以上是关于lettcode 上的几道哈希表与链表组合的数据结构题的主要内容,如果未能解决你的问题,请参考以下文章

《数据结构》顺序表与链表

顺序表与链表

顺序表与链表的基本操作

区别顺序表与链表 自我总结

数据结构——栈(线性表与链表)

数据结构 顺序表与链表 四部曲总汇