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 上的几道哈希表与链表组合的数据结构题的更多相关文章
- Java 哈希表(google 公司的上机题)
1 哈希表(散列)-Google 上机题 1) 看一个实际需求,google 公司的一个上机题: 2) 有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址..),当输入该 ...
- 什么叫哈希表(Hash Table)
散列表(也叫哈希表),是根据关键码值直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. - 数据结构 ...
- 深入理解PHP内核(六)哈希表以及PHP的哈希表实现
原文链接:http://www.orlion.ga/241/ 一.哈希表(HashTable) 大部分动态语言的实现中都使用了哈希表,哈希表是一种通过哈希函数,将特定的键映射到特定值得一种数据 结构, ...
- 数据结构 哈希表(Hash Table)_哈希概述
哈希表支持一种最有效的检索方法:散列. 从根来上说,一个哈希表包含一个数组,通过特殊的索引值(键)来访问数组中的元素. 哈希表的主要思想是通过一个哈希函数,在所有可能的键与槽位之间建立一张映射表.哈希 ...
- Redis源码研究:哈希表 - 蕫的博客
[http://dongxicheng.org/nosql/redis-code-hashtable/] 1. Redis中的哈希表 前面提到Redis是个key/value存储系统,学过数据结构的人 ...
- 哈希表的java实现
一.为什么要用哈希表 树的操作通常需要O(N)的时间级,而哈希表中无论存有多少数据,它的插入和查找(有时包括删除)只需要接近常量级的时间,即O(1)的时间级. 但是哈希表也有一定的缺点:它是基于数组的 ...
- 菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)
菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下) Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:B ...
- LeetCode刷题总结-哈希表篇
本文总结在LeetCode上有关哈希表的算法题,推荐刷题总数为12题.具体考察的知识点如下图: 1.数学问题 题号:149. 直线上最多的点数,难度困难 题号:554. 砖墙,难度中等(最大最小边界问 ...
- 菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)[转]
菜鸟nginx源码剖析数据结构篇(七) 哈希表 ngx_hash_t(下) Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...
随机推荐
- Python调用Matlab2014b引擎
用惯Python的你,是不是早已无法忍受matplotlib那丑陋无比的图以及蛋疼无比部署依赖? 当当当当,Matlab2014b的Python Engine API现已加入豪华午餐. 上次写了一篇文 ...
- 华为、华三 交换机 开启SNMP 简单网络管理协议
[huawei]snmp-agent //使能SNMP代理 [huawei]snmp-agent sys-info version all //允许所有SNMP 支持 协议 [huawei]snmp- ...
- Matlab状态模式
状态模式就是将状态的条件判断语句转化成其函数重写形式,利用了面向对象语言的多态性,本文根据https://blog.csdn.net/lm324114/article/details/78819602 ...
- C语言知识点拾遗
sleep() abort() fflush() getimeofday() sigaction() Linux 进程学习(四)------ sigaction 函数 malloc() 如何实现一个m ...
- Linux应用与端口
lsof -i:port --- 得到对应端口的应用pid PS -ef|grep pid --- 根据pid得到对应应用
- influxDB应用及TICK stack
InfluxData平台用于处理度量和事件的时间序列平台,常被称为TICK stack,包含4个组件:Telegraf,influxDB,Chronograf和Kapacitor,分别负责时间序列数据 ...
- 肖哥HCNP-正式篇笔记
21.网工学习环境准备. 一. 关掉所有杀毒软件及管家如阿健. 二. 安装环回网卡 (一定要先安装.) 1. 计算机设备管理 2. 在右侧最上端计算机名上方右键,点击过时硬件. 3. 下一步.手动选择 ...
- js正则表达式验证、匹配数字、匹配字符串、匹配中文、匹配任意字符备忘录
本文转自:91博客 :原文地址:http://www.9191boke.com/235792704.html 正则表达式或“regex”用于匹配字符串的各个部分,下面是我创建正则表达式的备忘录.包括一 ...
- centos7和centos6安装httpd
编译安装httpd http://apr.apache.org/download.cgi 下载 apr-util-1.6.1.tar.bz2 apr-1.6.5.tar.bz2 http://http ...
- 百度编译器ueditor插入视频的时候。在预览的窗口提示 “输入的视频地址有误,请检查后再试!
ueditor.all.js: 搜索me.commands["insertvideo"] 把html.push(creatInsertStr( vi.url, vi.width | ...