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 ...
随机推荐
- # - net - cannot access a disposed object r nobject name filebufferingreadstream
.Net Core 2.1-Cannot access a disposed object.Object name: 'IServiceProvider' (3) I just migrated .N ...
- Mysql表字段命令alter add
alter add命令用来增加表的字段. alter add命令格式:alter table 表名 add字段 类型 其他; 例如,在表MyClass中添加了一个字段passtest,类型为int(4 ...
- AutoFac的简单使用教程
Autofac可以对代码进行依赖注入,实现控制反转.以下是本菜鸟在初次入门时的代码配置,其源码,内部原理都还有待日后研究.目前也只是仅仅做到了能够使项目正常使用而已. 跟我一样刚刚入门的菜鸟朋友们可以 ...
- C# vb .net实现颜色替换效果滤镜
在.net中,如何简单快捷地实现Photoshop滤镜组中的颜色替换效果呢?答案是调用SharpImage!专业图像特效滤镜和合成类库.下面开始演示关键代码,您也可以在文末下载全部源码: 设置授权 第 ...
- Apple SIP简介及在Clover中如何控制
Apple SIP简介及在Clover中如何控制 来源 http://www.yekki.me/apple-sip-overview-and-how-to-disable-it-in-clover/ ...
- 关于AWK的10个经典案例
awk是Linux系统下一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件.数据排序.计算以及生成报表等等,应用非常广泛.基本的命令语法:awk option 'pattern {actio ...
- git恢复已删的分支
git恢复已经删除的分支 执行git命令, 找回之前提交的commit git log -g 执行效果 commit 80fd3a3e1abeab52030ee9f6ec32b5c815de20a9 ...
- Java 读取控制台输入
方式1:InputStreamReader+BufferedReader package my_package; import java.io.BufferedReader; import java. ...
- 使用SAP Cloud Application Programming模型开发OData的一个实际例子
刚刚过去的SAP TechEd上,SAP CTO Juergen Mueller向外界传递了一个重要的信息:身处云时代大环境下的SAP从业者,在SAP云平台上该如何选择适合自己的开发方式? Juerg ...
- php中call_user_func()与call_user_func_array()区别
call_user_func:把一个参数作为回调函数调用 用法说明: call_user_func ( callable $callback [, mixed $parameter [, mixed ...