Day7-哈希表,leetcode454,383,15,18
- 四数相加 II
-
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
-
0 <= i, j, k, l < n
-
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
-
思路:
-
暴力解法:4个for循环遍历这个数组,要取不同元素相加,如果等于0的话,有个count做一个++的操作,最后返回count
-
换个思路:先预处理前两个数组,再去处理后两个数组,然后再去集合里查找是否有我们想要的元素,所以考虑使用哈希法。两两数组分开遍历,比如先遍历A、B数组,将两个数组中取出的a+b进行计算,然后计算后的数值放到一个集合里,然后再遍历C、D两个数组,c+d,在遍历C、D数组的时候去判断上面存储的集合中有没有我们想要的元素,四个元素相加等于0,集合选择map结构,key存储a+b,value存储a+b的值出现的次数,遍历C、D时是需要检查target-(c+d),即0-(c+d)有没有出现在集合中,
-
- 用哈希表统计 nums1[i] + nums2[j] 的所有和及出现次数。
-
- 遍历 nums3 和 nums4,查找 -(nums3[i] + nums4[j]) 是否在哈希表中,有则累加出现次- 数。
-
- 返回总数。
var fourSumCount = function (nums1, nums2, nums3, nums4) {
const twoSumMap = new Map()
let count = 0
// 统计nums1和nums2所有和的出现次数
for (let i = 0; i < nums1.length; i++) {
for (let j = 0; j < nums2.length; j++) {
const sum = nums1[i] + nums2[j]
if (twoSumMap.has(sum)) {
twoSumMap.set(sum, twoSumMap.get(sum) + 1)
} else {
twoSumMap.set(sum, 1)
}
}
}
// 遍历nums3和nums4,查找-(sum)是否在map中
for (let i = 0; i < nums3.length; i++) {
for (let j = 0; j < nums4.length; j++) {
if (twoSumMap.has(0 - (nums3[i] + nums4[j]))) {
count+= twoSumMap.get(0 - (nums3[i] + nums4[j]))
}
}
}
return count
}
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;
};
- 赎金信
-
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。ransomNote 和 magazine 由小写英文字母组成
-
思路:
-
因为题目说只有小写字母,那可以采用空间换取时间的哈希策略,用一个长度为26的数组来记录magazine里字母出现的次数。
-
然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。
-
依然是数组在哈希法中的应用。
// 用数组下标做哈希,统计 magazine 字符出现次数。
// 检查 ransomNote 是否都能被 magazine 提供,不能则提前返回 false。即遍历 ransomNote,遇到一个字母就消耗一个 magazine 里的字母,若不足直接返回 false。
// 全部满足则返回 true。
var canConstruct = function(ransomNote, magazine) {
// 创建一个长度为 26 的数组 strArr,用来统计每个小写字母出现的次数。
// base 是字符 'a' 的 ASCII 码(97),用于做下标映射。
const strArr = new Array(26).fill(0),
base = "a".charCodeAt();
// 遍历 magazine,统计每个字母出现的次数,存到 strArr 对应下标。
for(const s of magazine) { // 记录 magazine里各个字符出现次数
strArr[s.charCodeAt() - base]++;
}
// 遍历 ransomNote,每遇到一个字符就让对应的计数减一。
// 如果发现某个字符计数为 0,说明 magazine 里没有足够的字符,直接返回 false。
for(const s of ransomNote) { // 对应的字符个数做--操作
const index = s.charCodeAt() - base;
if(!strArr[index]) return false; // 如果没记录过直接返回false
strArr[index]--;
}
// 如果所有字符都能满足需求,返回 true。
return true;
};
- 三数之和
-
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
-
注意:答案中不可以包含重复的三元组。
-
思路:
-
三数之和的双指针法,一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。
-
首先将数组排序,然后有一层for循环,从下标0的地方开始,同时定一个下标left定义在i+1的位置上,定义下标right在数组结尾的位置上,
-
a+b+c=0,a=nums[i],b=nums[left],c=nums[right]
-
若nums[i]+nums[left]+nums[right] > 0,说明此时三数之和大了,因为数组是排序过的,所以right应该向左移动,这样才能让三数之和小一些。
-
若nums[i]+nums[left]+nums[right]<0,说明三数之和小了,left就向右移动,才能让三数之和大一些,直到left与right相遇为止。
var threeSum = function(nums) {
const res = [], len = nums.length
// 将数组排序
nums.sort((a, b) => a - b)
for (let i = 0; i < len; i++) {
let l = i + 1, r = len - 1, iNum = nums[i]
// 数组排过序,如果第一个数大于0直接返回res
if (iNum > 0) return res
// 去重
// 遍历到下标 i 时,如果当前数字 iNum 和前一个数字 nums[i - 1] 相等,说明这个三元组的第一个数已经用过了,再用会产生重复结果。
// 所以直接 continue 跳过本次循环,避免重复三元组。
if (iNum == nums[i - 1]) continue
while(l < r) {
let lNum = nums[l], rNum = nums[r], threeSum = iNum + lNum + rNum
// 三数之和小于0,则左指针向右移动
if (threeSum < 0) l++
else if (threeSum > 0) r--
else {
res.push([iNum, lNum, rNum])
// 去重
while(l < r && nums[l] == nums[l + 1]){
l++
}
while(l < r && nums[r] == nums[r - 1]) {
r--
}
l++
r--
}
}
}
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);
};
- 四数之和
-
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
-
思路:
-
双指针法
-
剪枝
-
去重
-
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n2),四数之和的时间复杂度是O(n3) 。
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;
};