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 ...
随机推荐
- Jenkins+harbor+gitlab+k8s 部署maven项目
一.概述 maven项目部署流程图如下: 环境介绍 操作系统 ip 角色 版本 ubuntu-16.04.4-server-amd64 192.168.10.122 Jenkins+harbor Je ...
- golang基础学习-strings包常用函数学习
package main import ( "fmt" "strings" ) //StrFunc 字符串说明 func main() { var testSt ...
- 仅反射加载(ReflectionOnlyLoadFrom)的 .NET 程序集,如何反射获取它的 Attribute 元数据呢?
原文:仅反射加载(ReflectionOnlyLoadFrom)的 .NET 程序集,如何反射获取它的 Attribute 元数据呢? 平时我们获取一个程序集或者类型的 Attribute 是非常轻松 ...
- 【C#】 获取计算机的硬件信息
添加引用:System.Management /// <summary> /// 获取本机的MAC地址 /// </summary> /// <returns>&l ...
- ashx 接受 post json 请求
HttpContext.Current.Response.ContentType = "application/json"; HttpContext.Curr ...
- 30个关于Shell脚本的经典案例(中)
本文目录 11.iptables自动屏蔽访问网站频繁的IP 12.判断用户输入的是否为IP地址 13.判断用户输入的是否为数字 14.给定目录找出包含关键字的文件 15.监控目录,将新创建的文件名追加 ...
- 《区块链DAPP开发入门、代码实现、场景应用》笔记4——Ethereum Wallet中部署合约
账号创建完成之后,账号余额是0,但是部署合约是需要消耗GAS的,因此需要获取一定的以太币才能够继续本次实现.在测试网中获取以太币可以通过挖矿的方式,在开发菜单中可以选择打开挖矿模式,但是这需要将Syn ...
- 水泥caement单词
Caement英语单词,翻译为:水泥 中文名:水泥 外文名:caement 目录 释义 caement 读音:英 [sɪˈment] 美 [sɪˈmɛnt] Noun名词. 水泥; caement在英 ...
- Excel工作表密码保护的破解
操作步骤:打开Visual Basic编辑器,单击“插入-->模块“,将以下代码粘贴到模块中即可. Sub DelPassword() ActiveSheet.Protect DrawingOb ...
- 一个工作13年的SAP开发人员的回忆:电子科技大学2000级新生入学指南
让我们跟着Jerry的文章,一起回到本世纪初那个单纯美好的年代. 2000年9月,Jerry告别了自己的高中时代,进入到自己心目中的电子游戏大学,开始了四年的本科生活.每个新生,都拿到了这样一本薄薄的 ...