(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:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人