(6/60)哈希表理论基础、有效的字母异位词、两个数组的交集、快乐数、两数之和
哈希表理论基础
定义
哈希表是根据关键码的值而直接进行访问的数据结构。
特点是查询时间复杂度为O(1)。
哈希函数
哈希表存储过程:键---映射--->数值(索引)中,完成这个映射过程的函数。
哈希碰撞
哈希函数映射到同一位置时称为哈希碰撞。
解决方案有两种:
- 拉链法。在原位置上向后接链表存储。
- 线性探测法。在表中线性遍历下去寻找空位。
常见哈希结构
- 数组
- 集合(set)
- 映射(map)
C++提供的set数据结构
| 集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
|---|---|---|---|---|---|---|
| std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
| std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
| std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
C++提供的map数据结构
| 映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
|---|---|---|---|---|---|---|
| std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
| std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) |
| std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
(从上往下依次为:不可重复、可重复、不可重复且无序)
红黑树实现的key是有序的,但只能增删,不可修改。
总结
-
要快速判断元素是否存在于集合内时,要想到哈希法。
-
本质是空间换时间
有效字母异位词
leetcode:242. 有效的字母异位词
排序法
思路
很简单的想法,排序完之后对比俩字符串是否相等。
复杂度分析
时间复杂度:O(NlogN)。两个排序,单个排序O(NlogN)。
空间复杂度:O(1)。
注意点
略
代码实现
class Solution {
public:
// 排序法
bool isAnagram(string s, string t) {
sort(s.begin(),s.end());
sort(t.begin(),t.end());
if(s == t)
return true;
else
return false;
}
};
哈希法
思路
要判断某些元素出现次数是否相等,想到哈希法。
元素数量确定且小(26个字母),所以用数组的数据结构。
- 遍历一个字符串,对映射位置的元素++;再遍历另一个字符串,对映射位置的元素--
- 遍历映射数组,看元素是否还为初始化的值,全为初始值则两字符串为有效字母异位词,否则不是。
复杂度分析
时间复杂度:O(N+M)。两个并列循环,N、M分别是s、t的长度,加一个常数次的循环。
空间复杂度:O(1)。开了常数个(26)空间的数组。
注意点
-
数组初始化
-
使用花括号
{}初始化:cppCopy Codeint arr[3] = {1, 2, 3};这种方式可以用于静态数组和动态数组的初始化,也可以用于多维数组的初始化。
-
不使用花括号
{}初始化:cppCopy Codeint arr[3] = {0}; // 所有元素都初始化为0 int arr[3] = {}; // 所有元素都初始化为0 int arr[] = {1, 2}; // 数组长度自动推断为2这种方式也适用于静态数组和动态数组的初始化,但是不适用于多维数组的初始化。
-
使用数组下标逐个赋值:
cppCopy Codeint arr[3]; arr[0] = 1; arr[1] = 2; arr[2] = 3;这种方式可以用于静态数组和动态数组的初始化,也可以用于多维数组的初始化。
-
使用循环语句逐个赋值:
cppCopy Codeint arr[3]; for (int i = 0; i < 3; i++) { arr[i] = i + 1; }这种方式可以用于静态数组和动态数组的初始化,也可以用于多维数组的初始化。在多维数组的初始化时,需要嵌套多层循环。
-
-
数组不能直接.size()获取长度。可以通过
sizeof(arr)/sizeof(arr[0])的方式获取数组长度。
代码实现
class Solution {
public:
// 哈希法
bool isAnagram(string s, string t) {
// 用一个size为26的数组来映射字符出现次数
int hash[26] = {0}; // *数组初始化方式
// s中出现的字符对应位置++
for(int i = 0;i < s.size();i++){
hash[ s[i] -'a' ]++; // 细节,字母减去'a'刚好就映射到了0~25的数组下标
}
// t中出现的字符对应位置--
for(int j = 0;j < t.size();j++){
hash[ t[j] -'a' ]--;
}
// 如果最终数组元素全为0,则是字母异位词
for(int k = 0;k < 26;k++){
if(hash[k] != 0)
return false;
}
return true;
}
};
两个数组的交集
leetcode:349. 两个数组的交集
哈希法(集合)
思路
输出结果唯一且无序,直接念unordered_set身份证号了,所以返回结果用无序集转vector。
中间过程,交集如何找:
- 另设一个集合,遍历存入数组1的值。
- 遍历数组2,看数组2元素是否已经存在于中间集,如果是则将元素存入结果集。
复杂度分析
时间复杂度:O(N)。
空间复杂度:O(N+M)。N是nums1的大小,是nums1、nums2的交集大小。
注意点
- vector有构造函数可以用迭代器。
代码实现
class Solution {
public:
// 哈希法(集合)
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 结果集
unordered_set<int> temp_set; // 中间集(过程)
for(int num : nums1){
temp_set.insert(num);
}
for(int num : nums2){
if(temp_set.find(num) != NULL){
result_set.insert(num);
}
}
return vector<int>(result_set.begin(),result_set.end());
}
};
哈希法(数组)
思路
同集合法。已知数组长度<=1000时可以用数组来做映射。
复杂度分析
时间复杂度:O(N)。
空间复杂度:O(N)。N是两数组交集大小。
注意点
略。
代码实现
class Solution {
public:

浙公网安备 33010602011771号