关于哈希表
哈希表是存储键值对的集合。键是唯一的,值可以有多个。
平时编程开发算法中用到的哈希表的数据结构有数组,set,map。
多数元素
题目如图所示
解题思路
使用JavaScript的Map解题,创建哈希表,哈希表的键key是唯一的,值可以有多个,因此可以用map的key键来存储数组中的元素值,用map的值value来存储key出现的次数。当key出现次数value过半的时候,就返回key
code
/**
* @param {number[]} nums
* @return {number}
*/
// 哈希表
var majorityElement = function (nums) {
//half变量记录在数组中出现过半的次数
let half = nums.length / 2;
let map = new Map;
for (let num of nums) {
//如果key为num的元素存在
if (map.has(num)) {
//currNum记录key是num的元素的值(记录num出现次数)
let currNum = map.get(num);
map.set(num, currNum + 1)//哈希表中添加键值对
} else {
//如果map中不存在key为num的元素,那就添加num到map中
map.set(num, 1)
}
// 若map中key为num的元素出现过半次数,返回这个元素的key(即返回num)
if (map.get(num) > half) return num;
}
};
字母异位词分组
题目如图所示
解题思路
将原始数组strs的单词str进行切割排序,排序之后再重新拼接成单词,经过这样处理的单词记作key
排序之后的再拼接成单词
通过判断哈希表中是否存在key,存在的话就把当前遍历到的str放入键为key的值的数组的末尾。啥意思,也就是说
map.get(key):这部分通过键 key 从 map 中获取对应的值,这里因为键对应的值是一个数组,所以会返回这个数组。
.push(str):在获取到数组后,使用 .push 方法将字符串 str 添加到数组末尾。
不存在就把key和对应的str数组保存到map中。
map.has(key) ? map.get(key).push(str) : map.set(key, [str]);通过这一句发现我对于Map,Set和Array的一些方法不够熟悉,稍后总结一个关于这三者的学习笔记。
最后将 map 中的值转换成数组,其中每个子数组包含了具有相同键的字符串组。
code
/**
* @param {string[]} strs
* @return {string[][]}
*/
var groupAnagrams = function(strs) {
let map = new Map();
strs.forEach((str) => {
const key = str.split("").sort().join("");
map.has(key) ? map.get(key).push(str) : map.set(key, [str]);
});
return Array.from(map.values());
};
有效的字母异位词
题目如图所示
解题思路
使用一个大小为26的数组来计算s字符串字母出现的频度,数组大小设置为26是因为英文字母26个。
遍历t串,若t字符串中存在s字符串中的字母,频率-1,最后再遍历数组看频率是否为0,是就是字母异位词,否则就不是。
如果最后数组中有频率不为0的,说明s或者t字符串中有其他不一样的字母。
code
var isAnagram = function (s, t) {
// 若两字符串长度不同,那么一定不是字母异位词
if (s.length !== t.length) return false;
// 创建一个长度为26,默认值为0的数组
const arr = new Array(26).fill(0);
// 遍历s字符串,使用数组arr统计s字符串中字母出现频率
for (let i = 0; i < s.length; i++) {
arr[s[i].charCodeAt() - 'a'.charCodeAt()]++;
}
// 遍历t字符串,存在和s数组中相同的字母,频率-1
for (let i = 0; i < t.length; i++) {
arr[t[i].charCodeAt() - 'a'.charCodeAt()]--;
}
// 检查数组中所有元素是否都为0
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== 0) return false;
}
return true;
};
两个数组的交集
题目如图所示
解题思路
这个题如果给的测试用例是分散的跨度大的数据,那么使用set
若测试数据是1-1000之内的数,比较小,比较集中可以使用数组来解决,毕竟数组映射运算快。
下面将使用set和数组分别解决本题。
code-set
var intersection = function (nums1, nums2) {
// 将nums1放入哈希表nums1Set中
const nums1Set = new Set(nums1);
// 哈希表res存储结果
const res = new Set();
// 遍历nums2,如果哈希表中存在nums2中的元素,就将该元素放入res中
for (let i = 0; i < nums2.length; i++) {
if (nums1Set.has(nums2[i])) {
res.add(nums2[i])
}
}
// 转换成数组输出
return Array.from(res);
};
code-数组
若数组元素为1-1000之内的数字,可以考虑使用数据解决本题。
开辟数组frequency大小为略微大于1000的,1001-1005差不多都可以,数组元素初始值默认为0。
记录nums1数组里面的所有元素。遍历nums1,将nums1中所有元素在frequency中数组下标都是1,其他的都是0。
遍历nums2,判断哈希数组frequency中存在nums2这个元素,就把nums2中的这个元素放入res。
var intersection = function (nums1, nums2) {
// 新建足够容量数组0填充全部元素
let frequency = new Array(1002).fill(0);
//set存储结果去重
let res = new Set();
// 遍历nums1,将frequency中对应元素下标值设置为1 ,其余为0
for (let i = 0; i < nums1.length; i++) {
frequency[nums1[i]] = 1
}
// 遍历nums2,若frequency中存在nums2的元素,就把nums2的这个元素添加到结果
for (let i = 0; i < nums2.length; i++) {
if (frequency[nums2[i]] == 1) {
res.add(nums2[i])
}
}
// 转化为数组返回
return Array.from(res);
};
两数之和
题目如图所示
解题思路
每当遇到要判断元素是否出现过或者要判断这个元素是否在这个集合里出现过,第一反应就是用哈希方法解题。两数之和需要判断数组的元素和数组的元素的下标,用数组和set做哈希映射不可行(因为数组无法和set只可以存储值,可以一存储两个值)所以使用哈希map解题
查找元素是否在数组中出现过,元素做key,下标做value。
数组中不同下标元素相同出现重复值怎么办呢?
数组重复也没关系,题目说了仅返回一种结果。
code
var twoSum = function(nums, target) {
//迭代nums并将nums[i],index放入哈希表
const hash=new Map();
// 判断另一个数是否存在哈希表中
for(let i=0;i<nums.length;i++){
if(hash.has(target-nums[i])){
//存在就获取索引,返回值
return[hash.get(target-nums[i]),i] ;
}
hash.set( nums[i],i);
}
};
四数相加
题目如图所示
解题思路
在有效字母异位词里面用数组做哈希表,因为俩个数组里面出现的都是小写字母,数值是可控的,用数组的下标来做映射的话是可以的。
本题的数组元素可能比较大,考虑map和set。
因为需要统计nums1[i]+nums2[i]是否在集合里及其出现次数,和nums3[i]+nums4[i]做映射,用map。
首先将nums1[i]+nums2[i]的值加入map的key,value为出现次数,形象一点来说就是
nums1[1]+nums2[3]=3
nums1[2]+nums2[3]=3
nums1[3]+nums2[4]=3
在map里面nums1[i]+nums2[i]=3作为key对应出现次数为3,value就是3
遍历nums3[i]+nums4[i],看0-(nums3[i]+nums4[i])是否出现在map中
nums3[1]+nums4[2]=-3
count用于计数,count+=value
code
在初次提交代码的时候有组测试用例没有通过
nums1 =[-1,-1],nums2 =[-1,1],nums3 =[-1,1],nums4 =[1,-1]正确输出6,下面代码的输出是4。
var fourSumCount = function (nums1, nums2, nums3, nums4) {
// 使用哈希map保存前两个数组中元素和及其元素和出现次数
let map = new Map();
// 定义统计值count把map中key对应的value统计出来
let count = 0;
// 遍历前两个数组
for (let i = 0; i < nums1.length; i++) {
for (let i = 0; i < nums2.length; i++) {
const sum = nums1[i] + nums2[i];
// 将前两个数组元素之和加入map中,和为key,出现次数为value
map.set(sum, (map.get(sum)) || 0 + 1)
}
}
// 遍历后两个数组
for (let i = 0; i < nums3.length; i++) {
for (let i = 0; i < nums4.length; i++) {
let target = 0 - (nums3[i] + nums4[i]);
// 判断map中是否有后两个数组元素和的匹配值,使用计数器统计
if (map.has(target)) {
count += (map.get(target) || 0)
}
}
}
return count;
};
在提供的代码中,存在一个变量名重复的问题,导致了逻辑上的错误。在遍历前两个数组的过程中,有两个嵌套循环,它们都使用了相同的索引变量 i。这会导致第二个循环覆盖第一个循环中的 i 值,从而影响结果。要解决这个问题,只需要给其中一个循环的索引变量起一个新的名字。
另外一点,将数组之和放入哈希map中的时候注意括号问题 map.set(sum, (map.get(sum)) || 0 + 1)与 map.set(sum, ((map.get(sum)) || 0 )+ 1)
对于 map.set(sum, (map.get(sum)) || 0 + 1),首先会判断 map.get(sum) 的值是否为 false 值(即 undefined、null、0、NaN、‘’ 或 false),如果是,则将 0 + 1 的结果作为参数传递给 map.set(sum, …);
对于 map.set(sum, ((map.get(sum)) || 0 )+ 1),首先会判断 map.get(sum) 的值是否为 false 值(即 undefined、null、0、NaN、‘’ 或 false),如果是,则将 0 的结果赋给 map.get(sum),然后再对新的值加 1,并将结果作为参数传递给 map.set(sum, …)。
修改后的代码如下
var fourSumCount = function (nums1, nums2, nums3, nums4) {
let map = new Map();
let count = 0;
for (let i = 0; i < nums1.length; i++) {
for (let j = 0; j < nums2.length; j++) {
const sum = nums1[i] + nums2[j];
map.set(sum, (map.get(sum) || 0) + 1);
}
}
for (let i = 0; i < nums3.length; i++) {
for (let j = 0; j < nums4.length; j++) {
let target = -(nums3[i] + nums4[j]);
if (map.has(target)) {
count += map.get(target) || 0;
}
}
}
return count;
};
总结
在遇到算法题需要使用哈希方法解题的时候,优先考虑数组,因为数组比较快,set里面放键值对要做哈希运算,做映射这些都会浪费时间。
数组做映射是最直接的,运行速度也是最快的。但是如果哈希值比较大的话,很难提前确定数组的大小,随着数据的增加而动态增长数组的话会导致频繁的内存重新分配和数据的迁移,影响性能。所以如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费,这时候就考虑使用set。