哈希表理论基础
简介
哈希表/散列表(Hash table),数组就是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,一般哈希表都是用来快速判断一个元素是否出现集合里。
-
哈希表的内部实现原理:
- 哈希表通常由数组实现,每个数组元素称为桶(bucket)。
- 每个桶可以存储一个键值对,或者一个链表/红黑树(用于解决冲突)。
- 通过哈希函数将键映射到数组索引,实现快速查找。
-
哈希函数:
- 作用:将键转换为数组索引。
- 特点:相同的输入总是产生相同的输出;不同的输入应尽可能产生不同的输出。
- 实现:常见方法包括除留余数法、平方取中法、折叠法等。
-
哈希碰撞:
- 定义:不同的键通过哈希函数得到相同的数组索引。
- 解决方法:
- 链地址法:在冲突位置维护一个链表,存储所有哈希到该位置的元素。
- 开放寻址法:寻找下一个空位置存储冲突元素(线性探测、二次探测、双重哈希等)。
-
常见数据结构对比
| 数据结构 | 特点 | 访问方式 | 操作时间复杂度 |
|---|---|---|---|
| 数组 | 连续内存空间 | 通过索引直接访问元素 | 查找、插入、删除时间复杂度 O(n) |
| Set | 基于哈希表实现 | 存储唯一值,无重复 | 查找、插入、删除时间复杂度平均 O(1) |
| Map | 基于哈希表实现 | 存储键值对 | 查找、插入、删除时间复杂度平均 O(1) |
- 哈希法应用场景
-
当需要快速判断一个元素是否出现在集合中时
-
需要 O(1) 时间复杂度的查找、插入、删除操作时
-
处理需要去重的问题时
-
需要统计元素出现频率时
-
242.有效的字母异位词
题目
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1: 输入: s = "anagram", t = "nagaram" 输出: true
示例 2: 输入: s = "rat", t = "car" 输出: false
说明: 你可以假设字符串只包含小写字母。
解题思路
- 涉及到查询字符是否出现以及出现次数,考虑用哈希表
- 因为小写字母数量有限且映射为 ASCII 表即可转为数字形式对应数组key,即每个字符使用 charCodeAt 映射为数字并减去'a'在 ASCII 表上对应的数字即可将字符串上的字符信息映射到数组中。
- 需要将S和T两个字符串的内容分别映射为数组,再进行数组的比较,需要两次for循环,产生两个数组。
- 因为字符串映射到数组的过程高度一致,可以合并为映射到同个数组中进行数量增减操作,可将两字符串上字符的差异信息汇聚到一个数组上,最后根据该数组数据判断结果即可。
- 如果S和T字符的长度相等,则可以在单次for循环中同时完成两字符串到数组的转换过程,因此可以先对比两字符串长度是否相等,之后再进行for循环
- 最后查看记录两字符串字符信息的数组是否全部元素值都为0,如是则返回true,否则返回false
代码实现
function isAnagram(s: string, t: string): boolean {
if (s.length !== t.length) return false;
const infoList = Array(26).fill(0);
const privt = "a".charCodeAt(0);
for (let i = 0; i < s.length; i++) {
infoList[s.charCodeAt(i) - privt]++;
infoList[t.charCodeAt(i) - privt]--;
}
return infoList.every((i) => i === 0);
}
349. 两个数组的交集
题目
给定两个数组,编写一个函数来计算它们的交集。
说明: 输出结果中的每个元素一定是唯一的。 我们可以不考虑输出结果的顺序。
解题思路
为避免双重for循环暴力解法,可以采用空间换时间的方式,将一个入参数组转为哈希表set,遍历另一个数组,过程中当遍历的值存在与set中时则加入到存放结果的哈希表resSet中,最后在将结果哈希表resSet转换为数组并返回即可
代码实现
function intersection(nums1: number[], nums2: number[]): number[] {
const set = new Set<number>(nums1);
const resSet = new Set<number>();
for (let val of nums2) {
if (set.has(val)) {
resSet.add(val)
}
}
return Array.from(resSet)
};
202. 快乐数
题目
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
示例:
输入:19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
解题思路
- 建一个函数来计算数字每位平方和
- 使用哈希集合来检测循环。
- 不断计算平方和,直到结果为1或出现循环。
代码实现
function isHappy(n: number): boolean {
// 创建一个Set来存储已经出现过的数字,用于检测循环
const seen = new Set<number>();
// 当n不为1且没有出现循环时,继续计算
while (n !== 1 && !seen.has(n)) {
seen.add(n);
n = getSum(n);
}
// 如果最终n为1,则是快乐数
return n === 1;
}
// 辅助函数:计算数字每位的平方和
function getSum(num: number): number {
let sum = 0;
while (num > 0) {
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
}
1. 两数之和
题目
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例: 给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
解题思路
因为需要判断一个元素是否出现过,所以应选用哈希法;因为需要存储键和值,所以使用map结构;因为想要查询的对象是值(计算和是否等于目标值),所以key存放元素值,value存放该元素的下标
代码实现
function twoSum(nums: number[], target: number): number[] {
const map: Map<number, number> = new Map();
for (let i = 0; i < nums.length; i++) {
const index = map.get(target - nums[i]);
if (index !== undefined) {
return [index, i];
}
map.set(nums[i], i);
}
return [];
}