Leetcode Hot 100 哈希表题解
哈希表基础知识(STL标准库)
- 容器选择
unordered_map哈希表 vsunordered_set集合
unordered_set<K>:只有键的哈希集合。当需要去重,或者只关心元素存在吗才使用,因为它比map更加节省内存unordered_map<K, V>:完整的哈希表,当需要建立关联的时候使用- 避坑:面试或者刷题的时候,如果题目要求最后的输出结果必须要按照大小顺序排列,否则的话优先使用带ordered前缀的版本,因为它查找的时间复杂度是$O(1)$,另外它的底层就是哈希表。不带前缀的map/set底层是红黑树,查找时间复杂度是$O(logn)$,速度会被拖慢
- unoerder_map和unordered_set查看数据的方法
判断元素是否在哈希表/集合的两种标准方法:
count(key):返回0或1.最简单的判断方法:if(hash.count(key))find(key):返回迭代器。如果找到了,那么可以再一次用迭代器拿值,这一个是直接可以找到这个值是否存在的;如果没找到那就是返回hash.end(),代表着返回了哈希表最后面的无元素的值- 标准形式:
if(hash.find(key) != hash.end()),hash.end()后面是没有数据的
- 标准形式:
- 让代码更加便捷
- auto类型推导:可以不用手写冗长的迭代器类型。直接
auto it = mp.begin,让编译器帮忙干活 - for循环与引用
- 迭代器遍历:适用于所有STL容器
for(auto it = mp.begin(); it != mp.end; iit++){} - 基于范围的for循环,刷题最推荐
for(const int &num: nums){} // 直接操作原数据,不用多复制浪费时间复杂度,实现零拷贝
for(const auto &[key, value] : mp){} // 添加const是为了防止修改
- 迭代器遍历:适用于所有STL容器
- 排序
在处理异位词、合并区间等题目时候,头文件里面的sort是yyds
- 用法:
sort(v.begin(), v.end()),这里的v是一个列表 - 复杂度:时间$O(n log n)$,它是一个经过高度优化的内省排序
两数之和
- 难点:在采用传统的暴力求解的情况之下,会出现导致时间复杂度指数叠加$O(n^2)$
- 求解:哈希表在这里可以去查找当前target数字所需要的另一半,
如果说没有找到的话,它会将当前遍历的数字的位置和值进行存储,形成一个映射,为后面的数字进行准备 - 容器选择:在这里以为结果是数组的下标,而不是数组里面的值,而数组的值和数组的下表具有映射关系,值只是中间目标,所以采用具有映射关系的unordered_map更加好
|
字母异位词分组
- 难点:如何判断长得不同的字符串是为同一类(包含相同的字符串)
- 求解:观察给出的案例和答案,会发现其实是将字符串相同但排序不相同的归为一类,于是可以将一类排序的结果当作是哈希表/字典里面的键,因为他们排序之后样子都会是一致的,而原始的放进这个键对应的值数组,进行不同组别的分类,于是就可以的得出相关的结果
- 容器选择:因为是键值对,而键是字符串的大类,值是未排序这些字符串的数组,因此采用哈希表:
unordered_map<string, vector<string>>
|
最长连续数列
- 难点:单纯的排序会导致$O(nlogn)$的时间复杂度,而题目严格要求智能$O(n)$
- 求解:需要利用哈希表将每一个数据存储,那么查找哈希表该数据时候只会有$O(1)$的时间复杂度,那么首先利用单次遍历的循环也就是$O(1)$进行多次遍历之后,在
num-1找出第一个数字,而找出第一个数之后while(num_set.count(currentNum + 1))自然向后遍历,顺藤摸瓜,就可以查完整个连续序列的长度 - 容器选择:因为不需要返回出索引也就是下标,整个结果都是面对着集合里面的值进行查询的,所以不需要键值对的map,这里最好采用set集合的方式,因为不需要记录额外的信息,所以采用
unordered_set
|
总结:什么时候用集合,什么时候用哈希表
- 哈希表
- 需要关键的额外信息,简单来说存在映射的关系
- 找位置:两数之和,根据数值之和找出两个数的下标
- 统计频率:记录一个数组里面,高频数字的出现次数
- 分组归类:像是字符异位分组,将根据排序之后的字符串存档在同类的列表
- 需要更新状态:涉及到“如果这个数出现,就把它对应的计数加1”, 这种有增量更新的需求操作
- 需要关键的额外信息,简单来说存在映射的关系
- 集合
- 单纯成员的检查
- 防止重复的遍历
- 查缺补漏,需要知道某个数字是否在集合里
- 寻找序列起点(因为它只关心下次数据对于单词数据是否连续也就是仅仅大于1,因此无需要再多的重复数据),像最长连续数列,只需要判断第一个数是否存在即可顺藤摸瓜继续找
- 自动去重:只关心一个数组出现过什么数字,直接将数组丢进集合即可去重
- 单纯成员的检查