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 ...
随机推荐
- golang 之 sql
golang提供了sql包查询数据 建立连接 导入第三方包 import( "database/sql" _"github.com/go-sql-driver/mysql ...
- python使用自带模块httplib进行http请求
#-*- encoding:utf-8 -*- import httplib, time class httpRequest(): def __init__(self, headers, reques ...
- Oracle数据库Schema的简介
百度文库中 Schema 的解释: 数据库中的Schema,为数据库对象的集合,一个用户一般对应一个schema. 官方定义如下: A schema is a collection of databa ...
- 用HTML、CSS、JS制作圆形进度条(无动画效果)
逻辑 1.首先有一个圆:蓝色的纯净的圆,效果: 2.再来两个半圆,左边一个,右边一个将此蓝色的圆盖住,效果: 此时将右半圆旋转60°,就会漏出底圆,效果: 然后我们再用一个比底圆小的圆去覆盖这个大 ...
- 2019 用友网络java面试笔试题 (含面试题解析)
本人3年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条. 用友网络等公司offer,岗位是Java后端开发,最终选择去了 用友网络. 面试了很多家公司,感觉大部分公司考察 ...
- VUE基础回顾2
1.响应式 vue修改了每个添加到data上的对象,当该对象发生变化时vue会收到通知,从而实现响应式.对象的每个属性都会被替换为getter,setter方法. 有两种方式实现data对象的监听 ( ...
- Oracle数据库之四大语言
一.数据定义语言: 1.用于改变数据库结构,包括创建.更改和删除数据库对象: 2.命令: create table :创建 alter table 修改 drop table 删除表 truncate ...
- css-博客样式初体验
css学习一周后,写了个基础博客样式. 样式是出来了,但是在写的过程中感觉css写的杂乱无章,可能是写的太少了吧,条例不是很清除,只是在写的过程 中一个点一个点的套,感觉样式出来即可,没有做到由全局出 ...
- Xcode 10 Archive 时电脑卡死
Xcode 10 Archive Unity5.x 导出工程时电脑卡死.解决办法:Targets - Build Settings - Debug Information Format 设置成DWAR ...
- MySQL5.7应当注意的参数
简介: 本篇文章主要介绍 MySQL 初始化应当注意的参数,对于不同环境间实例迁移,这些参数同样应当注意. 注: 本文介绍的参数都是在配置文件 [mysqld] 部分. server_id 和 log ...