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 上的几道哈希表与链表组合的数据结构题的更多相关文章

  1. Java 哈希表(google 公司的上机题)

    1 哈希表(散列)-Google 上机题 1) 看一个实际需求,google 公司的一个上机题: 2) 有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址..),当输入该 ...

  2. 什么叫哈希表(Hash Table)

    散列表(也叫哈希表),是根据关键码值直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. - 数据结构 ...

  3. 深入理解PHP内核(六)哈希表以及PHP的哈希表实现

    原文链接:http://www.orlion.ga/241/ 一.哈希表(HashTable) 大部分动态语言的实现中都使用了哈希表,哈希表是一种通过哈希函数,将特定的键映射到特定值得一种数据 结构, ...

  4. 数据结构 哈希表(Hash Table)_哈希概述

    哈希表支持一种最有效的检索方法:散列. 从根来上说,一个哈希表包含一个数组,通过特殊的索引值(键)来访问数组中的元素. 哈希表的主要思想是通过一个哈希函数,在所有可能的键与槽位之间建立一张映射表.哈希 ...

  5. Redis源码研究:哈希表 - 蕫的博客

    [http://dongxicheng.org/nosql/redis-code-hashtable/] 1. Redis中的哈希表 前面提到Redis是个key/value存储系统,学过数据结构的人 ...

  6. 哈希表的java实现

    一.为什么要用哈希表 树的操作通常需要O(N)的时间级,而哈希表中无论存有多少数据,它的插入和查找(有时包括删除)只需要接近常量级的时间,即O(1)的时间级. 但是哈希表也有一定的缺点:它是基于数组的 ...

  7. 菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)

      菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)   Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:B ...

  8. LeetCode刷题总结-哈希表篇

    本文总结在LeetCode上有关哈希表的算法题,推荐刷题总数为12题.具体考察的知识点如下图: 1.数学问题 题号:149. 直线上最多的点数,难度困难 题号:554. 砖墙,难度中等(最大最小边界问 ...

  9. 菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)[转]

    菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下) Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...

随机推荐

  1. IntelliJ IDEA 换背景免费酷炫的插件(转)

    一.插件的安装 打开setting文件选择Plugins选项 Ctrl + Alt + S File -> Setting 分别是安装JetBrains插件,第三方插件,本地已下载的插件包. 二 ...

  2. 多次执行echarts时出现 there is a chart instance already initialized on the dom

    原因,多次使用 echarts.init(document.getElementById(this.options.zid)); 解决方案 设为全局

  3. 利用HashMap计算一个字符串中每个字符出现的次数

    问题描述:计算一个字符串中每个字符出现的次数 问题分析:每个字符串对应着它的次数,且字符串唯一不重复,这让我们想到了HashMap中的键值对. 1.使用Scanner获取字符串 2.遍历字符串,获取每 ...

  4. github上传本地项目代码

    进入github首页,点击新项目new repository,如下图所示: 然后进入如下页面,填写信息: 最后点击Create repository,生成如下页面: 红色圈圈画的步骤,先点击Clone ...

  5. Commander基本使用

    随着NodeJs的不断发展,对于前端来说要做的东西也就更多,Vue脚手架React脚手架等等等一系列的东西都脱颖而出,进入到人们的视野当中,对于这些脚手架工具来讲也只是停留在应用阶段,从来没有想过脚手 ...

  6. layui 框架 table插件 实现键盘快捷键 切换单元格编辑

    最近使用layui的框架时,发现table插件不支持键盘快捷键切换单元格,花了点时间实现此功能. 分享给有需要的朋友们~~~ 效果图 代码: 1.支持 enter,上,下,右键 切换单元格,支持隐藏列 ...

  7. property Alternative forms propretie

    property Alternative forms propretie English English Wikipedia has articles on: Property (disambigua ...

  8. Linux环境宿主机进入Docker容器、连接数据库、复制文件

    我们默认mysql容器已经正常启动,以下为关键命令.1.docker exec -it mysql bash : 进入已经正常启动的容器bash中,mysql是指实际容器名称.2.mysql -uro ...

  9. 云计算与大数据实验:Hbase shell操作成绩表

    [实验目的] 1)了解hbase服务 2)学会hbase shell命令操作成绩表 [实验原理] HBase是一个分布式的.面向列的开源数据库,它利用Hadoop HDFS作为其文件存储系统,利用Ha ...

  10. Python语言防坑小技巧

    Python语言防坑小技巧 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.赋值即定义  1>.运行以下代码会出现报错 #!/usr/bin/env python #_*_ ...