哈希表
什么是哈希表
首先什么是哈希表,哈希表又称为散列表。直白来讲,数组就是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。
哈希表的作用
一般哈希表都是用来快速判断一个元素是否出现在集合里。时间复杂度为O(1),但是是牺牲了空间换取了时间,因为我们需要额外的数组,set或者map来存放数据,才可以实现快速的查找。
常见的三种哈希结构
- 数组
- set 集合
- map 映射
1. 有效的字母异位词
- leetCode242 有效的字母异位词
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
思路
- 声明计数器,一个对象 const obj = {}
- 遍历s字符串,如果遍历到字符串的’a’字母,去看obj[a]是否存在
- 不存在说明第一次遍历到’a’字母,那么初始化obj[a] = 1
- 如果存在则obj[a] += 1
- t字符串同理,它每次减1
- 遍历完s字符串后,遍历obj对象,看它的每一对key:value,是否value都是0
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
var isAnagram = function(s, t) {
const sLen = s.length
const tLen = t.length
if (sLen !== tLen) {
return false
}
const obj = {}
for (let i = 0; i < sLen; i++) {
const currentS = s[i]
const currentT = t[i]
obj[currentS] ? obj[currentS]++ : (obj[currentS] = 1)
obj[currentT] ? obj[currentT]-- : (obj[currentT] = -1)
}
return Object.values(obj).every((v) => v === 0)
}
console.log(isAnagram('ana', 'aan'))
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
var isAnagram = function(s, t) {
let map = new Map()
if (s.length !== t.length) {
return false
}
for (var i = 0; i < s.length; i++) {
if (!map.has(s[i])) {
map.set(s[i], 1)
} else {
map.set(s[i], map.get(s[i]) + 1)
}
}
for (var j = 0; j < t.length; j++) {
if (!map.has(t[j]) || map.get(t[j]) === 0) {
return false
} else {
map.set(t[j], map.get(t[j]) - 1)
}
}
return true
}
console.log(isAnagram('ana', 'aan'))
- leetCode 383赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
思路
也就是看后面的字母串的字母是否包含前一个字符串的字母,与上题类似
/**
* @param {string} ransomNote
* @param {string} magazine
* @return {boolean}
*/
var canConstruct = function(ransomNote, magazine) {
let map = {}
for (let i = 0; i < magazine.length; i++) {
map[magazine[i]] ? map[magazine[i]]++ : (map[magazine[i]] = 1)
}
for (let i = 0; i < ransomNote.length; i++) {
map[ransomNote[i]] ? map[ransomNote[i]]-- : (map[ransomNote[i]] = -1)
}
// console.log(Object.values(map)) //[1,0]
return Object.values(map).every((d) => d >= 0)
}
console.log(canConstruct('ab', 'aab'))
记录相关数组的迭代方法
- forEach
对数组中每个元素运行一次提供的函数
函数接收三个参数:
el
当前循环到的元素
index
当前循环元素的下标
arr
数组本身
后两个参数可选,没有返回值
const arr = [1, 2, 3]
arr.forEach((el, index, arr) => {
console.log(`${el}, ${index},`, arr)
})
/*
输出如下
1, 0, [1, 2, 3]
2, 1, [1, 2, 3]
3, 2, [1, 2, 3]
*/
- map
该方法将原数组按照一定的规则(即提供的回调函数)映射为一个新数组
返回一个新数组,不改变原数组的值
const arr = [1, 2, 3]
const newArr = arr.map((el) => {
return el * 2
})
console.log(newArr) //[ 2, 4, 6 ]
const arr = [1, 2, 3]
const newArr = arr.map((el) => {
// return el * 2
})
//如果没有返回值会返回map
console.log(newArr) //[ undefined, undefined, undefined ]
- filter
一般用过滤数组
对数组每一项进行运算,返回运算结果为true的元素
返回一个新数组,不改变原数组的值
const arr = [1, 2, 3, 4, 5]
const newArr = arr.filter((el, index, arr) => {
return el > 2
})
console.log(newArr) // [ 3, 4, 5 ]
- some 返回boolean值,只要其中一个满足条件,就返回true
const arr = [1, 2, 3, 4, 5]
const result = arr.some((el, index, arr) => {
return el > 2
})
console.log(result) //true
//全部都不满足就返回false
const result1 = arr.some((el, index, arr) => {
return el > 5
})
console.log(result1) //false
- every,与some相反,只有全部满足条件才会返回true,否则false
const arr = [1, 2, 3, 4, 5]
const result = arr.every((el, index, arr) => {
return el > 2
})
console.log(result) //false
- reduce
array.reduce(callback,[initialValue])
callback接受4个参数
- accumulatar 上一次调用回调返回的值,或者是提供的初始值(initialValue)
- currentValue 数组中正在处理的元素
- currentIndex 数组中正在处理的元素索引,如果提供了initialValue,从0开始,否则,从1开始
- array 数组对象本身
例如,使用reduce
方法实现累加和
const arr = [1, 2, 3, 4]
const result = arr.reduce((a, b) => {
return a + b
})
console.log(result) //10
运行过程如下
const arr = [1, 2, 3, 4]
const result = arr.reduce((accumulator, currentValue, currentIndex, array) => {
// accumulator:上一次回调后的结果
// currentValue:目前要进行运算的值
// currentIndex:目前的index
console.log(accumulator, currentValue, currentIndex)
return accumulator + currentValue
})
console.log(result)
/*运行结果
1 2 1
3 3 2
6 4 3
10
*/
此时我们把初始值传入
const arr = [1, 2, 3, 4]
const result = arr.reduce((accumulator, currentValue, currentIndex, array) => {
// accumulator:上一次回调后的结果
// currentValue:目前要进行运算的值
// currentIndex:目前的index
console.log(accumulator, currentValue, currentIndex)
return accumulator + currentValue
}, 10)
console.log(result)
/*
10 1 0
11 2 1
13 3 2
16 4 3
20
*/
reduceRight与reduce类似,不同之处在于它是从最后一个值开始计算的。
const arr = [1, 2, 3, 4]
const result = arr.reduceRight(
(accumulator, currentValue, currentIndex, array) => {
// accumulator:上一次回调后的结果
// currentValue:目前要进行运算的值
// currentIndex:目前的index
console.log(accumulator, currentValue, currentIndex)
return accumulator + currentValue
},
10
)
console.log(result)
/*
10 4 3
14 3 2
17 2 1
19 1 0
20
*/
- find/findIndex
find方法用于找出第一个符合条件的数组成员,返回该成员
const arr = [1, 2, 3, 4, 5]
const result = arr.find((el, index, arr) => {
return el > 3
})
console.log(result) // 4
findIndex和这个方法类似,不同之处在于,findIndex是返回第一个符合条件的下标
const arr = [1, 2, 3, 4, 5]
const result = arr.findIndex((el, index, arr) => {
return el > 3
})
console.log(result) // 3。因为4的下标是3
(咳咳,走远咯走远咯,开始下一题)
- leetCode49 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
思路
也就是说,把含有相同字母的单词分为一组
- 定义map存储
- 字符串转数组->排序->转字符串为key(将所有异位词排序为同一单词,作为key)
- 根据key插入map
- map传数组输出
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())
}
console.log(groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat']))
- leetCode 438 找到字符串中所有字母异位词
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指字母相同,但排列不同的字符串。
思路
- 这个方法会超时
/**
* @param {string} s
* @param {string} p
* @return {number[]}
*/
var findAnagrams = function(s, p) {
let result = []
let arr = p.split('').sort()
for (let i = 0; i < s.length - p.length + 1; i++) {
temp = s
.substring(i, i + p.length)
.split('')
.sort()
if (temp.toString() == arr.toString()) {
result.push(i)
}
}
return result
}
console.log(findAnagrams('cbaebabacd', 'abc'))
2. 两个数组的交集
- leetCode 349
给定两个数组,编写一个函数来计算它们的交集。
思路
解法一
- 同集合对num1去重
- 遍历num1,筛选出nums2也包含的值
var intersection = function(nums1, nums2) {
return [...new Set(nums1)].filter((n) => nums2.includes(n))
}
时间复杂度filter+includes,O(m*n), 空间复杂度O(m)
解法二
- 新建一个字典,遍历nums1, 填充字典
- 遍历nums2,遇到字典里的值就选出,并从字典中删除
var intersection = function(nums1, nums2) {
const map = new Map();
nums1.forEach(n => {
map.set(n, true)
})
const res = []
nums2.forEach(n => {
if(map.get(n)){
res.push(n)
map.delete(n)
}
})
return res;
};
时间复杂度O(m+n), 空间复杂度:O(m)
解法三 排序加双指针
- 首先将数组排序
- 定义一个数组保存结果result
- 两个指针分别指向两个数组的头部,每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位。如果两个数字相等,且该数字不等于result的尾部元素,同时将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束
- 解释下为啥与result的尾部元素比较,因为数组nums1,nums2是经过升序排序的,所以result也必然是升序的,所以新遍历的元素如果不等于result尾部元素,必然大于尾部元素,且不会与result内元素重复(因为是升序的)。
var intersection = function(nums1, nums2) {
nums1.sort((a, b) => a - b);
nums2.sort((a, b) => a - b);
const length1 = nums1.length, length2 = nums2.length;
let index1 = 0, index2 = 0;
const result = [];
while (index1 < length1 && index2 < length2) {
const num1 = nums1[index1], num2 = nums2[index2];
if (num1 === num2) {
// 保证加入元素的唯一性
if (!result.length || num1 !== result[result.length - 1]) {
result.push(num1);
}
index1++;
index2++;
} else if (num1 < num2) {
index1++;
} else {
index2++;
}
}
return result;
};
时间复杂度:O(mlogm+nlogn)
空间复杂度:O(logm+logn),其中 m 和 n 分别是两个数组的长度。空间复杂度主要取决于排序使用的额外空间
3. 快乐数
- leetCode 202快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 true ;不是,则返回 false
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
思路
1。 如果map中出现了这个数,证明出现了环路,就返回false
/**
* @param {number} n
* @return {boolean}
*/
var isHappy = function (n) {
let map = new Map();
let t = 0;
//当map里面有这个元素的时候说明判断进入了循环,永远不可能到1
while (!map.has(n)) {
map.set(n, true);
n = n + '';
for (let i = 0; i < n.length; i++) {
t += (n[i] - 0) * (n[i] - 0);
}
n = t;
t = 0;
if (n === 1) {
return true;
}
}
//默认结果是false
return false;
};
4. 两数之和
- leetCode 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
var twoSum = function(nums, target) {
const _length = nums.length;
const _mayMap = new Map();
for (let i = 0; i < _length; i++) {
if (_mayMap.has(target - nums[i])) {
return [_mayMap.get(target - nums[i]), i];
}
_mayMap.set(nums[i], i);
}
};
5. 三数之和
- leetCode 15 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路
采用的是双指针
var threeSum = function(nums) {
const result = []
let newArr = [...nums].sort((a, b) => a - b)
for (let i = 0; i < newArr.length - 2; i++) {
if (i === 0 || newArr[i] !== newArr[i - 1]) {
let start = i + 1
let end = newArr.length - 1
while (start < end) {
if (newArr[i] + newArr[start] + newArr[end] == 0) {
result.push([newArr[i], newArr[start], newArr[end]])
start++
end--
while (start < end && newArr[start] == newArr[start - 1]) {
start++
}
while (start < end && newArr[end] == newArr[end + 1]) {
end--
}
} else if (newArr[i] + newArr[start] + newArr[end] < 0) {
start++
} else {
end--
}
}
}
}
return result
}
let result = threeSum([-1, 0, 1, 2, -1, -4])
console.log(result)
6. 四数之和
- leetCode 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] :0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
var fourSum = function(nums, target) {
nums = nums.sort(function(x, y) {
return x - y
})
let result = []
console.log(nums)
for (let i = 0; i < nums.length - 3; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue
for (let j = i + 1; j < nums.length - 2; j++) {
if (j > i + 1 && nums[j] === nums[j - 1]) continue
let start = j + 1
let end = nums.length - 1
while (start < end) {
if (nums[i] + nums[j] + nums[start] + nums[end] === target) {
result.push([nums[i], nums[j], nums[start], nums[end]])
start++
end--
while (start < end && nums[start] === nums[start - 1]) {
start++
}
while (start < end && nums[end] === nums[end + 1]) {
end--
}
} else if (nums[i] + nums[j] + nums[start] + nums[end] < target) {
start++
} else {
end--
}
}
}
}
return result
}