哈希法
什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。比如需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
有效的字母异位词
-
var isAnagram = function(s, t) { let map = new Map() for(let item of s){ map.set(item, (map.get(item)||0) + 1) } let size = map.size for(let item of t){ console.log(map.get(item)) if(map.get(item)){ map.set(item,map.get(item) - 1) if(map.get(item)==0) size-- continue } return false } return size === 0? true : false };
牛马算法
-
修改:定义一个数组叫做record用来上记录字符串s里字符出现的次数。
需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。
那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。
那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。
var isAnagram = function(s, t) {
if(s.length !== t.length) return false;
const resSet = new Array(26).fill(0);
const base = "a".charCodeAt();
for(const i of s) {
resSet[i.charCodeAt() - base]++;
}
for(const i of t) {
if(!resSet[i.charCodeAt() - base]) return false;
resSet[i.charCodeAt() - base]--;
}
return true;
};
两个数组的交集
-
不知道为啥 一遇到去重 我就想用set。。。但是直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。
-
var intersection = function(nums1, nums2) { let set1 = new Set(nums1) let set2 = new Set(nums2) let set3 = new Set() for(let item of set1){ if(set2.has(item)){ set3.add(item) } } return [...set3] };
-
var intersection = function(nums1, nums2) { // 根据数组大小交换操作的数组 if(nums1.length < nums2.length) { const _ = nums1; nums1 = nums2; nums2 = _; } const nums1Set = new Set(nums1); const resSet = new Set(); // for(const n of nums2) { // nums1Set.has(n) && resSet.add(n); // } // 循环 比 迭代器快 for(let i = nums2.length - 1; i >= 0; i--) { nums1Set.has(nums2[i]) && resSet.add(nums2[i]); } return Array.from(resSet); };
-
但是要注意,使用数组来做哈希的题目,是因为题目都限制了数值的大小。
而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
快乐数
-
var isHappy = function(n) { function getSum(num) { let sum = 0 while(num){ let temp = num % 10 sum += temp*temp num = Math.floor(num/10) } return sum } let set = new Set() n = getSum(n) while(n!==1 && !set.has(n)){ set.add(n) n = getSum(n) } return n===1? true: false };
-
这道题。em 没什么好说的
两数之和!
-
万物起源之第一题 两数之和!典中典
-
因为本题,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
再来看一下使用数组和set来做哈希法的局限。
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
-
var twoSum = function(nums, target) { let map = new Map() for(let i = 0 ; i < nums.length ; i++){ if(map.has(target - nums[i])){ return [map.get(target - nums[i]) , i] } map.set(nums[i],i) } };
三数之和
-
这题双指针算法,之前刷过 印象比较深刻,记得判断去重复逻辑
var threeSum = function(nums) { let res = [] nums.sort((a,b) => a-b) if(nums.length < 3) return res for(let i = 0 ; i < nums.length - 2 ; i++){ if(nums[i] > 0) return res if(i>0&&nums[i] == nums[i-1]) continue let left = i + 1 let right = nums.length - 1 while(left < right) { if(left < right && nums[i] + nums[left] + nums[right] < 0){ left++ continue } if(left < right && nums[i] + nums[left] + nums[right] > 0){ right-- continue } res.push([nums[i],nums[left],nums[right]]) left++ right-- while(left< right && nums[left] === nums[left-1]) left++; while(left < right && nums[right] === nums[right+1]) right--; } } return res };
-
使用了一下两数之和的方法 超时
var threeSum = function(nums) { let res = [] nums.sort((a,b) => a-b) if(nums.length < 3) return res for(let i = 0 ; i < nums.length - 2 ; i++){ if(nums[i] > 0) return res if(i>0&&nums[i] == nums[i-1]) continue let target = -nums[i] let set = new Set() for(let j = i + 1 ; i < nums.length ; j ++){ if(j>i+1 && nums[j]==nums[j-1]) continue if(set.has(target - nums[j])){ res.push([nums[i],num[j],target-num[j]]) } set.add(nums[j]) } } return res };
-
随想录整理的递归法 看了一遍没动手写
/** * nsum通用解法,支持2sum,3sum,4sum...等等 * 时间复杂度分析: * 1. n = 2时,时间复杂度O(NlogN),排序所消耗的时间。、 * 2. n > 2时,时间复杂度为O(N^n-1),即N的n-1次方,至少是2次方,此时可省略排序所消耗的时间。举例:3sum为O(n^2),4sum为O(n^3) * @param {number[]} nums * @return {number[][]} */ var threeSum = function (nums) { // nsum通用解法核心方法 function nSumTarget(nums, n, start, target) { // 前提:nums要先排序好 let res = []; if (n === 2) { res = towSumTarget(nums, start, target); } else { for (let i = start; i < nums.length; i++) { // 递归求(n - 1)sum let subRes = nSumTarget( nums, n - 1, i + 1, target - nums[i] ); for (let j = 0; j < subRes.length; j++) { res.push([nums[i], ...subRes[j]]); } // 跳过相同元素 while (nums[i] === nums[i + 1]) i++; } } return res; } function towSumTarget(nums, start, target) { // 前提:nums要先排序好 let res = []; let len = nums.length; let left = start; let right = len - 1; while (left < right) { let sum = nums[left] + nums[right]; if (sum < target) { while (nums[left] === nums[left + 1]) left++; left++; } else if (sum > target) { while (nums[right] === nums[right - 1]) right--; right--; } else { // 相等 res.push([nums[left], nums[right]]); // 跳过相同元素 while (nums[left] === nums[left + 1]) left++; while (nums[right] === nums[right - 1]) right--; left++; right--; } } return res; } nums.sort((a, b) => a - b); // n = 3,此时求3sum之和 return nSumTarget(nums, 3, 0, 0); };
四数之和
-
就是在三数之和基础上嵌套一层for循环而已
var fourSum = function(nums, target) { const len = nums.length; if(len < 4) return []; nums.sort((a, b) => a - b); const res = []; for(let i = 0; i < len - 3; i++) { // 去重i if(i > 0 && nums[i] === nums[i - 1]) continue; for(let j = i + 1; j < len - 2; j++) { // 去重j if(j > i + 1 && nums[j] === nums[j - 1]) continue; let l = j + 1, r = len - 1; while(l < r) { const sum = nums[i] + nums[j] + nums[l] + nums[r]; if(sum < target) { l++; continue} if(sum > target) { r--; continue} res.push([nums[i], nums[j], nums[l], nums[r]]); // 对nums[left]和nums[right]去重 while(l < r && nums[l] === nums[++l]); while(l < r && nums[r] === nums[--r]); } } } return res; };
四数相加||
-
var fourSumCount = function(nums1, nums2, nums3, nums4) { let map = new Map() for(let i = 0 ; i < nums1.length ; i++){ for(let j = 0 ; j < nums2.length ; i++){ let sum = nums1[i] + nums2[j] map.set(sum , (map.get(sum)||0) + 1) } } let res = 0 for(let i = 0 ; i < nums3.length ; i++){ for(let j = 0 ; j < nums4.length ; j++){ let sum = nums3[i] + nums4[j] res += (map.get(-sum)||0) } } return res };
-
人家迷惑 这样居然是超时的。。。。
-
-
var fourSumCount = function(nums1, nums2, nums3, nums4) { const twoSumMap = new Map(); let count = 0; // 统计nums1和nums2数组元素之和,和出现的次数,放到map中 for(const n1 of nums1) { for(const n2 of nums2) { const sum = n1 + n2; twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1) } } // 找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来 for(const n3 of nums3) { for(const n4 of nums4) { const sum = n3 + n4; count += (twoSumMap.get(0 - sum) || 0) } } return count; };
-
然后卡哥的代码就不超时了 没看出什么区别。
赎金信
-
太简单了 跟上面题 一样 没什么好说的
-
var canConstruct = function(ransomNote, magazine) { let map = new Map() for(let i of magazine){ map.set(i,(map.get(i)||0)+1) } for(let j of ransomNote){ if(!map.has(j)||map.get(j)==0) return false map.set(j,map.get(j)-1) } return true };
哈希法
什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。比如需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
有效的字母异位词
-
var isAnagram = function(s, t) { let map = new Map() for(let item of s){ map.set(item, (map.get(item)||0) + 1) } let size = map.size for(let item of t){ console.log(map.get(item)) if(map.get(item)){ map.set(item,map.get(item) - 1) if(map.get(item)==0) size-- continue } return false } return size === 0? true : false };
牛马算法
-
修改:定义一个数组叫做record用来上记录字符串s里字符出现的次数。
需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。
那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。
那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。
var isAnagram = function(s, t) {
if(s.length !== t.length) return false;
const resSet = new Array(26).fill(0);
const base = "a".charCodeAt();
for(const i of s) {
resSet[i.charCodeAt() - base]++;
}
for(const i of t) {
if(!resSet[i.charCodeAt() - base]) return false;
resSet[i.charCodeAt() - base]--;
}
return true;
};
两个数组的交集
-
不知道为啥 一遇到去重 我就想用set。。。但是直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。
-
var intersection = function(nums1, nums2) { let set1 = new Set(nums1) let set2 = new Set(nums2) let set3 = new Set() for(let item of set1){ if(set2.has(item)){ set3.add(item) } } return [...set3] };
-
var intersection = function(nums1, nums2) { // 根据数组大小交换操作的数组 if(nums1.length < nums2.length) { const _ = nums1; nums1 = nums2; nums2 = _; } const nums1Set = new Set(nums1); const resSet = new Set(); // for(const n of nums2) { // nums1Set.has(n) && resSet.add(n); // } // 循环 比 迭代器快 for(let i = nums2.length - 1; i >= 0; i--) { nums1Set.has(nums2[i]) && resSet.add(nums2[i]); } return Array.from(resSet); };
-
但是要注意,使用数组来做哈希的题目,是因为题目都限制了数值的大小。
而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
快乐数
-
var isHappy = function(n) { function getSum(num) { let sum = 0 while(num){ let temp = num % 10 sum += temp*temp num = Math.floor(num/10) } return sum } let set = new Set() n = getSum(n) while(n!==1 && !set.has(n)){ set.add(n) n = getSum(n) } return n===1? true: false };
-
这道题。em 没什么好说的
两数之和!
-
万物起源之第一题 两数之和!典中典
-
因为本题,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
再来看一下使用数组和set来做哈希法的局限。
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
-
var twoSum = function(nums, target) { let map = new Map() for(let i = 0 ; i < nums.length ; i++){ if(map.has(target - nums[i])){ return [map.get(target - nums[i]) , i] } map.set(nums[i],i) } };
三数之和
-
这题双指针算法,之前刷过 印象比较深刻,记得判断去重复逻辑
var threeSum = function(nums) { let res = [] nums.sort((a,b) => a-b) if(nums.length < 3) return res for(let i = 0 ; i < nums.length - 2 ; i++){ if(nums[i] > 0) return res if(i>0&&nums[i] == nums[i-1]) continue let left = i + 1 let right = nums.length - 1 while(left < right) { if(left < right && nums[i] + nums[left] + nums[right] < 0){ left++ continue } if(left < right && nums[i] + nums[left] + nums[right] > 0){ right-- continue } res.push([nums[i],nums[left],nums[right]]) left++ right-- while(left< right && nums[left] === nums[left-1]) left++; while(left < right && nums[right] === nums[right+1]) right--; } } return res };
-
使用了一下两数之和的方法 超时
var threeSum = function(nums) { let res = [] nums.sort((a,b) => a-b) if(nums.length < 3) return res for(let i = 0 ; i < nums.length - 2 ; i++){ if(nums[i] > 0) return res if(i>0&&nums[i] == nums[i-1]) continue let target = -nums[i] let set = new Set() for(let j = i + 1 ; i < nums.length ; j ++){ if(j>i+1 && nums[j]==nums[j-1]) continue if(set.has(target - nums[j])){ res.push([nums[i],num[j],target-num[j]]) } set.add(nums[j]) } } return res };
-
随想录整理的递归法 看了一遍没动手写
/** * nsum通用解法,支持2sum,3sum,4sum...等等 * 时间复杂度分析: * 1. n = 2时,时间复杂度O(NlogN),排序所消耗的时间。、 * 2. n > 2时,时间复杂度为O(N^n-1),即N的n-1次方,至少是2次方,此时可省略排序所消耗的时间。举例:3sum为O(n^2),4sum为O(n^3) * @param {number[]} nums * @return {number[][]} */ var threeSum = function (nums) { // nsum通用解法核心方法 function nSumTarget(nums, n, start, target) { // 前提:nums要先排序好 let res = []; if (n === 2) { res = towSumTarget(nums, start, target); } else { for (let i = start; i < nums.length; i++) { // 递归求(n - 1)sum let subRes = nSumTarget( nums, n - 1, i + 1, target - nums[i] ); for (let j = 0; j < subRes.length; j++) { res.push([nums[i], ...subRes[j]]); } // 跳过相同元素 while (nums[i] === nums[i + 1]) i++; } } return res; } function towSumTarget(nums, start, target) { // 前提:nums要先排序好 let res = []; let len = nums.length; let left = start; let right = len - 1; while (left < right) { let sum = nums[left] + nums[right]; if (sum < target) { while (nums[left] === nums[left + 1]) left++; left++; } else if (sum > target) { while (nums[right] === nums[right - 1]) right--; right--; } else { // 相等 res.push([nums[left], nums[right]]); // 跳过相同元素 while (nums[left] === nums[left + 1]) left++; while (nums[right] === nums[right - 1]) right--; left++; right--; } } return res; } nums.sort((a, b) => a - b); // n = 3,此时求3sum之和 return nSumTarget(nums, 3, 0, 0); };
四数之和
-
就是在三数之和基础上嵌套一层for循环而已
var fourSum = function(nums, target) { const len = nums.length; if(len < 4) return []; nums.sort((a, b) => a - b); const res = []; for(let i = 0; i < len - 3; i++) { // 去重i if(i > 0 && nums[i] === nums[i - 1]) continue; for(let j = i + 1; j < len - 2; j++) { // 去重j if(j > i + 1 && nums[j] === nums[j - 1]) continue; let l = j + 1, r = len - 1; while(l < r) { const sum = nums[i] + nums[j] + nums[l] + nums[r]; if(sum < target) { l++; continue} if(sum > target) { r--; continue} res.push([nums[i], nums[j], nums[l], nums[r]]); // 对nums[left]和nums[right]去重 while(l < r && nums[l] === nums[++l]); while(l < r && nums[r] === nums[--r]); } } } return res; };