概括
哈希系列在LeetCode热题中只有3个,在算法题中都是比较容易的题目。
哈希在JS中的表现就是使用Map/Set数据结构,某种程度上,使用Object对象也能达到这个效果,因为object的属性要保持唯一性。 哈希一般用来处理以下类型的题目:
- 去重
- 分组
- 复杂度为O(n)或者O(1)的查找
我们来解一下LeetCode上的题目
题目一
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
难度:简单
输入: nums = [2,7,11,15], target = 9
输出: [0,1]
解释: 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
输入: nums = [3,2,4], target = 6
输出: [1,2]
输入: nums = [3,3], target = 6
输出: [0,1]
无废话解法
这个题目属于典型的O(1)查找的场景,也是比较简单的一个题目。
假如我们找的是9,遍历到2的时候,只需要看9-2是否存在。遍历到11的时候,只需要判断9-11是否存在。
首先我们想到使用哈希Map,其中数组中的数字本身就是Map的key,而数字对应的下标就是value。
需要注意,在找数字之和的题目中,这个题目之所以简单,是存在以下几个条件:
-
数组中无重复数字
-
只需要一个结果
-
满足taget的数字,只要两个,且同一个数字不能重复使用
后面我们会遇到更复杂的求和题目,这时候使用哈希已经不管用了。
JS 实现
const getSumRes =(nums,target) =>{
const map = new Map();
for(let i = 0;i<nums.length;i++){
const num = nums[i];
const diff = target - num;
if(map.has(diff)){
return [map.get(diff),i]
}else{
map.set(num,i)
}
}
}
console.log(getSumRes([2,7,11,15],9))
console.log(getSumRes([3,2,4],6))
console.log(getSumRes([3,3],6))
题目二
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
难度:简单
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
输入: strs = [""]
输出: [[""]]
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i] 仅包含小写字母
无废话解法
这个题目属于典型的分组场景。
使用Map进行分组,就需要找到唯一key。异位词首先是他们包含的字母是一样的,只是顺序不同,我们只需要将这些字母重新排序,就可以得到唯一key。如:"eat"和"tea"的key,都是 “aet”。
所以,我们的一句话解法就是: 遍历数组,将每个单词的字母排序,排序的结果作为分组的key。
JS 实现
// 先实现排序,将排序的结果作为分组的key
const getKey = (word) => {
return word.split("").sort().join('')
}
//主逻辑
const groupAnagrams = (strList) => {
// 实现分组,首先想到的是使用Map。
// 更简单的方式是使用lodash 的group方法,但是面试没法这么用。
const res = new Map();
strList.forEach(item => {
const key = getKey(item)
// 当map中不存在key时,就追加一下
if (res.has(key)) {
res.get(key).push(item)
res.set(key, res.get(key))
} else {
// 当map中不存在key时,就新增这个key
res.set(key, [item])
}
})
return res.values()
};
console.log(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]))
console.log(groupAnagrams(["a"]))
console.log(groupAnagrams([""]))
题目三
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) **的算法解决此问题。
难度:简单
输入: nums = [100,4,200,1,3,2]
输出: 4
解释: 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
输入: nums = [0,3,7,2,5,8,4,6,0,1]
输出: 9
无废话解法
这是一个复杂度为O(n)的查找。这里要理解这个"连续最长序列",它的含义是‘1234’这种连续的,‘135’就不算。 所以我们最直接的思路就是:
- 遍历数组
- 每遍历到一个数字n,就看一下n+1是否存在,如果存在则再看n+2是否存在,依次类推 题目要求复杂度是O(n),但是我们第一步就已经O(n)了。因此,第2步就必须使用哈希结构,达到O(1)的复杂度。
JS 实现
const getMaxLength = (nums)=>{
let maxLength = 0
// 先将数组转换为Set结果,这样第2步查询的时候,复杂度为O(1)
let set = new Set(nums)
// 遍历数组
for(let i = 0;i<nums.length;i++){
// 判断是否存在n-1,如果有,说明这个数字已经被探过了,就继续遍历下一个
//这里可能有点不好理解,需要结合下面的逻辑。
//假如存在数组[1,3,2],我们遍历到1的时候,已经嗅探过2和3是存在的。所以,当我们遍历到2的时候就只需判断一下2-1是否存在,如果存在,说明2已经被嗅探过了,就不用处理了。这样效率高一点。
if(set.has(nums[i]-1)){
continue
}
let length = 1
//循环判断n+1,n+2...是否存在
while(set.has(nums[i]+length)){
length++
}
maxLength = Math.max(maxLength,length)
}
return maxLength
}
console.log(getMaxLength([100,4,200,1,3,2]))
console.log(getMaxLength([0,3,7,2,5,8,4,6,0,1]))