哈希表
哈希表都是用来快速判断一个元素是否出现集合里.
常见的三种哈希结构
· 数组
· set (集合)
· map(映射)
快速判断一个元素是否出现集合里的时候,就要考虑哈希法
哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找
数组就是简单的哈希表,但是数组的大小可不是无限开辟的
242. 有效的字母异位词 简单题
异位词:字符串s和t中每个字符出现的次数都相同
字母异位词:重新排列源单词的字母得到的一个新单词
字符串s和t仅包含小写字母
思路:
暴力求解: 时间复杂度至少得O(n^2)
哈希表: 时间复杂度O(n),空间复杂度为O(1) 以空间换时间
因为串s和t只包含小写字母,那么可以定义一个数组(数组是简单的哈希表),来记录串s中每个字符出现的次数,数组大小为26,初始化为0。 (数组大小是常数级别 ,空间复杂度为O(1))
要想是异位词 首先两个字符串的长度得相等
遍历串s, s[i]-'a'下标所对应的元素+1,然后遍历串t,t[i]-'a'下标所对应的元素-1,如果-1后元素值小于0,说明t中有某个字母出现的次数大于s中出现的次数,不是字母异位词,返回false
如果能遍历完t,则是有效的字母异位词
注意: console.log('b'-'a'); // NaN
会先将‘b’ 转换成number类型,那么就是NaN, NaN-NaN ==> NaN
var isAnagram = function(s, t) {
if(s.length != t.length) return false;
let arr = new Array(26).fill(0);
// 注意: 不要直接 ‘b’-‘a’ ==> NaN
let base = 'a'.charCodeAt(); // a --> 97
for(let i of s) {
arr[i.charCodeAt()-base]++;
}
for(let i of t){
arr[i.charCodeAt()-base]--;
if(arr[i.charCodeAt()-base]<0) return false;
}
return true;
};
349. 两个数组的交集 简单题
返回两个数组 nums1 和 nums2的交集;输出结果中的每个元素一定是唯一的, 不考虑输出结果的顺序.
==>理解为: 求两个数组公共的部分,去重.
var intersection = function(nums1, nums2) {
let set = new Set();
let nums1Set = new Set(nums1);
for(let k of nums2) {
nums1Set.has(k) && set.add(k);
}
return Array.from(set);
};
350. 两个数组的交集 II 简单题
==>理解为: 求两个数组公共的部分,不去重.
var intersect = function(nums1, nums2) {
let res = new Array();
let map = new Map();
for(let k of nums1){
value = map.get(k) ? map.get(k)+1:1;
map.set(k,value);
}
for(let k of nums2){
let value = map.get(k)? map.get(k):0;
if(value > 0) {
map.set(k,value-1);
res.push(k);
}
}
return res;
};
先用map记录下第一个数组中的元素放在key,各元素出现的次数放在value. 然后再遍历第二个数组,能在map中找到对应元素,则添加这个元素到结果数组里. value值大于1,HashMap中的value值减1,表示已经找到一个相同的了. 如果value值等于1,则删除该元素.
202. 快乐数 简单题
判断一个数n是不是快乐数.
快乐数定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数
var getSum = function(n) {
let s = 0;
while(n) {
s += (n%10)**2;
n = Math.floor(n/10);// 注意一定要取整,不然会超时
}
return s;
}
var isHappy = function(n) {
let set = new Set(); // Set() 里的数是惟一的
// 如果在循环中某个值重复出现,说明此时陷入死循环,也就说明这个值不是快乐数
// 循环结束条件 只要能判断出n是否是快乐数就结束循环
//n == 1 -->是
//set中有重复数就一定能判断n不是快乐数 结束循环
while (n !== 1 && !set.has(n)) {
set.add(n);
n = getSum(n);
}
return n === 1;
};
1. 两数之和 简单题
在数组中找出 和为目标值target的那两个整数,并返回它们的数组下标
var twoSum = function(nums, target) {
let map = new Map();
for(let i = 0; i < nums.length; i++) {
if(map.has(target-nums[i])) {
return [i,map.get(target-nums[i])];
}
map.set(nums[i],i);
}
};
454. 四数相加 II 中等题
四个独立的数组,只要找到A[i]+B[j]+C[K]+D[l] =0就可以,不用考虑有重复的四个元素相加等于0的情况.
var fourSumCount = function(nums1, nums2, nums3, nums4) {
let map = new Map();
let count=0;
for(let i of nums1) {
for(let j of nums2){
// let value = map.get(i+j) ? map.get(i+j)+1 : 1;
// map.set(i+j,value);
map.set(i+j,(map.get(i+j) || 0)+1);
}
}
for(let i of nums3) {
for(let j of nums4){
if(map.has(-(i+j))){
count = count + map.get(-(i+j));
}
}
}
return count;
};
383. 赎金信 简单题
给你两个字符串:ransomNote 和 magazine ,判断ransomNote能不能由magazine 里面的字符构成.
如果可以,返回 true ;否则返回 false.
magazine 中的每个字符只能在 ransomNote 中使用一次.
注意:由小写英文字母组成
var canConstruct = function(ransomNote, magazine) {
if(ransomNote.length > magazine.length) return false;
let res = new Array(26).fill(0);
let base = 'a'.charCodeAt();
for(let i of magazine){
res[i.charCodeAt()-base]++;
}
for(let i of ransomNote){
res[i.charCodeAt()-base]--;
if(res[i.charCodeAt()-base]<0) return false;
}
return true;
};
15. 三数之和 中等题
判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k,同时还满足 nums[i] + nums[j] + nums[k] == 0.
注意:答案中不可以包含重复的三元组.
注意[0,0,0,0] 这组数据, 还有 难点在于如何去除重复解
方法一: 哈希解法(不推荐,去重难搞)
两层for循环可以确定a和b的数值了,哈希法来确定0-(a+b)是否在数组里出现过,但题目中说不可以包含重复的三元组,需要去重,而去重比较繁琐,容易超时
方法二: 双指针法:(比哈希高效)O(n^2)
1.将数组升序排序,i从0到nums.length-2,left定义在i+1的位置上,right定义在数组结尾nums.length-1的位置上
2.找nums[i]+nums[left]+nums[right]=0
3.>0,right向左移,让三数之和变小
4.<0,left向右移,让三数之和变大
5.直到left与right相遇
var threeSum = function(nums) {
// 不重复的三元组 (a,b,c) ===> 理解为 排序后 a,b,c (a去重,b去重,c去重)
let len = nums.length;
// 长度小于3
if(len < 3) return [];
// nums数组 排升序
nums.sort((a,b) => a-b);
// res 用于存放结果集
let res = [];
for(let i = 0; i < len-2; i++){
if(nums[i] > 0) break;
// a 去重
// if(nums[i] == nums[i-1]) continue;
while(nums[i] == nums[i-1]) i++;
//使用双指针
let l = i+1,r = len-1;
while(l < r) {
let sum = nums[i] + nums[l] + nums[r];
if(sum > 0) {r--;continue;}
if(sum < 0) {l++;continue;}
// sum = 0 找到满足条件的 a,b,c, 存入res
res.push([nums[i],nums[l],nums[r]]);
// b,c 去重
while(l < r && nums[l] == nums[++l]);
while(l < r && nums[r] == nums[--r]);
}
}
return res
};
不重复的三元组(a,b,c) ===> 理解为 排序后 a,b,c (a去重,b去重,c去重)
排序的目的: 让满足条件的三个元素(a,b,c)是排序的
a,b,c去重: 保证(a,b,c)只会出现一次,从而就实现了去重
18. 四数之和 中等题
由n个整数组成的数组nums,和一个目标值target.
找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复)
var fourSum = function(nums, target) {
let len = nums.length;
// 长度小于4
if(len < 4) return [];
// res 用于存放结果
let res = [];
// nums排升序
nums.sort((a,b) => a-b);
for(let i = 0; i < len - 3; i++) {
if(nums[i] > target && target >=0) break;// 此处剪枝 可写可不写
// a 去重
while(i>0 && nums[i] == nums[i-1])i++;
for(let j = i+1; j < len - 2; j++) {
// 此处剪枝 可写可不写
if(nums[i]+nums[j] > target && target >= 0) break;
// b 去重
while(j > i+1 && nums[j] == nums[j-1])j++;
// 此时已固定 a+b
// 双指针 滑动
let l = j+1,r=len-1;
while(l < r) {
let sum = nums[i] + nums[j] + nums[l] + nums[r];
if(sum > target) {r--; continue;}
if(sum < target) {l++; continue;}
// 找到满足条件的a,b,c,d
res.push([nums[i],nums[j],nums[l],nums[r]]);
// c,d去重
while(l < r && nums[l] == nums[++l]);
while(l < r && nums[r] == nums[--r]);
}
}
}
return res;
};
三数之和的双指针解法是一层for循环nums[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i]+nums[left]+nums[right]==0.
四数之和的双指针解法是两层for循环nums[k]+nums[i]为确定值,依然是循环内有left和right下标作为双指针,找到满足条件的四个数.
三数之和的时间复杂度是o(n^2),四数之和的时间复杂度是o(n^3).
同理,五数之和、六数之和等等都采用这种解法.
对于三数之和 双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法.
四数之和 双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法.