前言
大家好,今天是@LetItbeSun 坚持每日两题的第18天。(😀
705和706也许让我们拥有了一个快乐周末,虽然因为个人原因,本人最近的刷题情绪有些不佳,但是还是要复习一下哈希。
孩子学会的东西总是忘怎么办,打一顿就好了!
先说这两道题
其实就是让我们复习一下哈希,朋友们,拿出算法 第4版(3.4 294页)开始看,大概看了一下这章的内容
解决705 706题
题目
难度简单146
不使用任何内建的哈希表库设计一个哈希集合(HashSet)。
实现 MyHashSet
类:
void add(key)
向哈希集合中插入值key
。bool contains(key)
返回哈希集合中是否存在这个值key
。void remove(key)
将给定值key
从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
示例:
输入: ["MyHashSet", "add", "add", "contains", "contains", "add", "contains", "remove", "contains"] [[], [1], [2], [1], [3], [2], [2], [2], [2]] 输出: [null, null, null, true, false, null, true, null, false] 解释: MyHashSet myHashSet = new MyHashSet(); myHashSet.add(1); // set = [1] myHashSet.add(2); // set = [1, 2] myHashSet.contains(1); // 返回 True myHashSet.contains(3); // 返回 False ,(未找到) myHashSet.add(2); // set = [1, 2] myHashSet.contains(2); // 返回 True myHashSet.remove(2); // set = [1] myHashSet.contains(2); // 返回 False ,(已移除)
提示:
0 <= key <= 106
- 最多调用
104
次add
、remove
和contains
。
思路
方法一 超大数组
这个方法应该是本能就能想到的方法不再赘述,这两个题也可以这么做,直接用key索引数组
缺点是空间复杂度太高
散列表是算法在时间和空间上作出权衡的经典例子。如果没有内存限制,我们可以直接将键作为(可能是一个超大的)数组的索引,那么所有查找操作只需要访问内存一次即可完成。但这种理想情况不会经常出现,因为当键很多时需要的内存太大。另一方面,如果没有时间限制,我们可以使用无序数组并进行顺序查找,这样就只需要很少的内存。而散列表则使用了适度的空间和时间并在这两个极端之间找到了一种平衡。事实上,我们不必重写代码,只需要调整散列算法的参数就可以在空间和时间之间作出取舍。我们会使用概率论的经典结论来帮助我们选择适当的参数。
——《算法4》3.4
方法二 拉链法
其实就是数组串链表,用哈希减小数组的长度,串上链表解决碰撞,也就是哈希值一样的数
比如 哈希 n%10 那么 数字10、100、1000的哈希值都一样
哈希值的选择
将整数散列最常用方法是除留余数法。我们选择大小为素数M的数组,对于任意正整数k,计算k除以M的余数。这个函数的计算非常容易(在Java中为k%M)并能够有效地将键散布在0到M-1的范围内。如果M不是素数,我们可能无法利用键中包含的所有信息,这可能导致我们无法均匀地散列散列值。例如,如果键是十进制数而M为10,那么我们只能利用键的后k位,这可能会产生一些问题。举个简单的例子,假设键为电话号码的区号且M=100。由于历史原因,美国的大部分区号中间位都是0或者1,因此这种方法会将大量的键散列为小于20的索引,但如果使用素数97,散列值的分布显然会更好(一个离100更远的素数会更好),如右侧所示。与之类似,互联网中使用的IP地址也不是随机的,所以如果我们想用除留余数法将其散列就需要用素数(2的幂除外)大小的数组。
——《算法4》3.4
705题解
class MyHashMap {
public:
/** Initialize your data structure here. */
vector<list<pair<int,int>>> mp; //数组串链表
const int n=769;
int Hash(int x){
return x%n;
}
MyHashMap() {
mp.resize(n);
}
/** value will always be non-negative. */
void put(int key, int value) {
//先查找链表里有无
int t=Hash(key);
for(auto it=mp[t].begin();it!=mp[t].end();it++){
//查找链表
if(key==it->first){
it->second=value;
return;
}
}
mp[t].push_back(make_pair(key,value));
}
/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
int get(int key) {
int t=Hash(key);
for(auto it=mp[t].begin();it!=mp[t].end();it++){
//查找链表
if(key==it->first){
return it->second;
}
}
return -1;
}
/** Removes the mapping of the specified value key if this map contains a mapping for the key */
void remove(int key) {
int t=Hash(key);
for(auto it=mp[t].begin();it!=mp[t].end();it++){
//查找链表
if(key==it->first){
mp[t].erase(it);
return;
}
}
}
};