哈希表的定义
哈希表(Hash Table)也叫散列表,是根据关键码值(Key value)而直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
思路和方法:
- 设计合适的哈希函数:确保元素能够较为均匀地分布在哈希表中,减少冲突。
- 处理冲突:常见的方法有链地址法、开放地址法等。当发生冲突时,根据所采用的冲突处理策略进行相应操作。
- 注意边界情况:如空表、元素不存在等情况。
例如,给定一组数字和一个目标值,要求找出两数之和等于目标值的两个数的索引。可以通过遍历数组,将每个数及其索引存储在哈希表中,在遍历过程中检查目标值与当前数的差值是否在哈希表中,从而找到答案。再比如,判断字符串中是否存在重复字符,可以利用哈希表记录每个字符是否出现过,若出现重复则说明有重复字符。
经典的哈希表应用题目:
题目 1:给定一个整数数组和一个目标值,找出数组中和为目标值的两个数的索引。
题目 2:判断一个字符串是否为回文,只考虑字母和数字,忽略大小写。
题目 3:找出数组中出现次数超过一半的元素。
题目 4:给定一系列单词,判断哪些单词是重复出现的。
题目 5:给定两个字符串,判断它们是否由相同的字符组成(字符出现的次数相同)。
题目 6:有一组学生的姓名和成绩,快速找出某个学生的成绩。
题目 7:在一个大文件中找出重复出现的行。
题目 8:计算一个字符串中每个字符出现的次数。
哈希映射
用于快速存储和检索数据的数据结构。通过将键(Key)映射到特定的值(Value),利用哈希函数来确定键在内存中的存储位置,从而实现高效的查找、插入和删除操作。
特点:
- 快速的查找性能,平均情况下可以在常数时间内完成查找。
- 能够处理大量的数据。
- 可以方便地存储键值对关系。
哈希表与哈希映射的区别
表述上:
- “哈希表”更强调其作为一种数据结构的整体特性,包括存储元素的方式和相关操作。
- “哈希映射”则更突出键值对之间的映射关系。
应用场景侧重:
- 哈希表可能更侧重于强调高效的数据存储和检索。
- 哈希映射可能在强调键与值的对应关系以及基于此进行的操作方面更突出一些。
常常可以互换,核心原理和功能是一致的,都是通过哈希函数来实现高效的键值关联存储和访问。
哈希函数
哈希函数(Hash Function)是一种将任意大小的数据映射到固定大小的一个值(称为哈希值或散列值)的函数。
特点:
- 确定性:相同的输入总是产生相同的哈希值。
- 快速计算:能够相对快速地计算出哈希值。
作用:
- 数据索引:用于构建哈希表等数据结构,以便快速定位和检索数据。
- 数据完整性验证:可以通过对比哈希值来验证数据是否被篡改。
比如,常见的简单哈希函数可以通过对输入数据进行某种运算(如求和、取模等)来得到哈希值。一个简单的例子是,将一个整数除以 10 取余数作为哈希值。
题目解析——最长连续序列
class Solution { public: int longestConsecutive(vector<int>& nums) { int res=0; unordered_set<int>num_set(nums.begin(),nums.end()); int len; for(int num:num_set){ if(!num_set.count(num-1)){ len=1; while(num_set.count(++num)) len++; res=max(res,len); } } return res; } };
- 初始化: 声明结果变量
res
初始化为0,用于存储最长连续序列长度。使用unordered_set<int> num_set
将输入数组转换为集合,自动去除重复元素,并提供O(1)的查找效率。 - 遍历集合: 使用范围for循环遍历
num_set
中的每个元素num
。集合中的元素保证了唯一性,避免了重复处理相同数值。 - 检查连续序列的起始点: 对于每个
num
,首先使用!num_set.count(num-1)
检查num
是否可能是连续序列的起始点。如果集合中不存在num-1
,意味着num
可能是新序列的起点。 - 计算连续序列长度: 一旦找到起始点,就进入一个while循环,不断检查并累加序列长度
len
。在循环中,通过num_set.count(++num)
检查num+1
是否在集合中,如果是,则len
加1,并将num
递增,继续检查下一个数。这一过程直到遇到不再连续的数字为止。 - 更新最长连续序列长度: 在每次发现并计算完一个连续序列后,使用
res=max(res,len)
更新最长连续序列长度。这样可以保证res
始终存储的是遍历过程中遇到的最长连续序列长度。 - 返回结果: 遍历结束后,返回最长连续序列的长度
res
。
这段代码通过利用集合的快速查找特性,简化了逻辑,高效地实现了寻找最长连续序列的功能。它避免了显式地存储和更新每个元素的连续序列长度,而是通过一次遍历集合并动态计算连续序列长度来直接求解问题。
class Solution { public: int longestConsecutive(vector<int> &nums) { if (nums.size() == 0) return 0; std::unordered_map<int, int> ret_table; int ret = 0; for (auto it : nums) { if (ret_table[it]) continue; ret_table[it + ret_table[it + 1]] = ret_table[it - ret_table[it - 1]] = ret_table[it] = ret_table[it - 1] + ret_table[it + 1] + 1; ret = std::max(ret , ret_table[it]); } return ret; } };
- 初始化: 首先检查输入数组
nums
的大小,如果为空,则返回0,表示没有连续序列。 - 定义哈希表: 使用
std::unordered_map<int, int>
,键(key)存储数组中的整数,值(value)表示以该整数为结尾的连续序列的长度。初始时,所有可能的整数在哈希表中都没有记录。 - 遍历数组: 对于数组中的每个元素
it
:如果it
已经在哈希表中(即ret_table[it]
非零),说明已处理过此元素相关的连续序列,直接跳过 - 合并连续序列:
- 如果
it - 1
在哈希表中(表示it
可能属于左侧的连续序列),并且it + 1
不在哈希表中,那么将左侧序列向右扩展1位,更新it
的值为左侧序列长度加1,并相应地更新哈希表中序列起始点的值。 - 如果
it + 1
在哈希表中(表示it
可能属于右侧的连续序列),并且it - 1
不在哈希表中,类似地,将右侧序列向左扩展1位。 - 如果
it - 1
和it + 1
都在哈希表中,说明当前元素同时属于两个连续序列,此时应合并这两个序列。通过更新两端序列的端点值,以及当前元素的值为合并后的序列长度。
- 更新最长连续序列长度: 在每次处理完一个元素后,用当前处理的最长连续序列长度(
ret_table[it]
)与全局最长连续序列长度(ret
)比较,较大者为新的最长连续序列长度。 - 返回结果: 遍历结束后,返回最长连续序列的长度
ret
。
- 实际代码中直接执行了序列合并的部分(未区分左右序列的情况),并直接计算合并后序列的长度,这要求在执行到这一步之前,左右两侧的连续序列已经通过之前的迭代被正确识别和记录。逐步检查每个元素,根据其相邻元素是否已存在于哈希表中来决定是创建新序列、延长已有序列还是合并序列,同时不断更新最长连续序列的长度。