454.四数相加 II
题目链接:四数相加 II
文档讲解:代码随想录/四数相加 II
视频讲解:视频讲解-四数相加 II
状态:已完成(1遍)
解题过程
看到题目的第一想法
我的第一想法是没有想法。感觉如果往哈希表上靠的话,那应该是在遍历的过程中用哈希表存出现过的和,最后在哈希表中查有多少和为0。但我想不出来如何操作。
看完代码随想录之后的想法
还是没有类比的思维。其实就和两数之和很像,两数之和是遍历的时候看看当前的数的目标数是否存在哈希表中;四数之和,就是在遍历前两个数组的时候存和的情况,再遍历后两个数组的时候去哈希表中找有没有符合条件的数。又因为不仅要统计是否出现,还要统计出现过的次数,所以使用map。
自己试着手搓一版:
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @param {number[]} nums3
* @param {number[]} nums4
* @return {number}
*/
var fourSumCount = function(nums1, nums2, nums3, nums4) {
let hashMap = {};
let ans = 0;
for(let i = 0;i<nums1.length;i++){
for(let j =0;j<nums2.length;j++){
hashMap[nums1[i]+nums2[j]] =hashMap[nums1[i]+nums2[j]]? hashMap[nums1[i]+nums2[j]]+1:1;//如果没有,就赋1,如果有,就加1
}
}
for(let i = 0;i<nums3.length;i++){
for(let j =0;j<nums4.length;j++){
let targetNum = 0 - (nums3[i]+nums4[j])
if(hashMap[targetNum]){
ans += hashMap[targetNum];
}
}
}
return ans;
};
提交发现没有问题,再看看文字讲解版代码。
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @param {number[]} nums3
* @param {number[]} nums4
* @return {number}
*/
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;
};
确实看上去要更简介一些,学到了学到了,用标准写法的set和get,以及||(虽然我还是更喜欢用?:)
总结
这道题其实很适合和两数之和来对比,两数之和和四数之和完全就是一个乘以2的关系,可惜在初遇这道题的时候没有想到,这下印象十分深刻了。
383.赎金信
题目链接:383. 赎金信
文档讲解:代码随想录/ 赎金信
视频讲解:无
状态:已完成(1遍)
解题过程
看到题目的第一想法
对构成者的那个字符串进行遍历,出现的字母计入哈希表中(Map),再对被构成的字符串进行遍历,看看哈希表中是否有遍历的单个字母,如果有且值大于0,那就把值减1,如果没有或者等于0,直接输出false;最后全部遍历完成后如果没输出false,那就输出true。
/**
* @param {string} ransomNote
* @param {string} magazine
* @return {boolean}
*/
var canConstruct = function(ransomNote, magazine) {
const hashMap = new Map();
for(let mag of magazine){
hashMap.set(mag,(hashMap.get(mag)||0)+1)
}
for(let note of ransomNote){
if(hashMap.get(note)>0){
hashMap.set(note,(hashMap.get(note))-1)
}else return false;
}
return true;
};
提交没毛病,芜湖起飞。
看完代码随想录之后的想法
得,忘记了这种小型的数据应该用哈希表的数组法来处理。
讲解代码如下:
/**
* @param {string} ransomNote
* @param {string} magazine
* @return {boolean}
*/
var canConstruct = function(ransomNote, magazine) {
const strArr = new Array(26).fill(0),
base = "a".charCodeAt();
for(const s of magazine) { // 记录 magazine里各个字符出现次数
strArr[s.charCodeAt() - base]++;
}
for(const s of ransomNote) { // 对应的字符个数做--操作
const index = s.charCodeAt() - base;
if(!strArr[index]) return false; // 如果没记录过直接返回false
strArr[index]--;
}
return true;
};
总结
使用数组来做哈希的题目,是因为题目都限制了数值的大小!!!!尤其是这种查字母的!
15.三数之和
题目链接:15.三数之和
文档讲解:代码随想录/三数之和
视频讲解:视频讲解-三数之和
状态:已完成(2遍)
解题过程
看到题目的第一想法
我一开始认为,首先用一个双层for循环将数组中所有两数之和用哈希表记录下来,然后再用一个for循环遍历,看看哈希表中有没有目标值,但是我的困难出现在:记录的时候,键是两数之和,但是我没办法记录是由哪两个数相加而成的;如果有重复的两数之和出现时,我该怎么区分不同的数但是相同的和?想了半天解决不了,看看代码随想录吧。
看完代码随想录之后的想法
果然用哈希还是太复杂了吗,用双指针更好,题解里甚至都没有JS版本的哈希法,那就直接看双指针吧:
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
// 去重
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
};
总结
这道题用哈希确实太过于复杂了,用双指针巧妙地在一次for循环中,设立两个一左一右的指针,往中间夹。确实没想到这个思路,天天哈希,脑子已经成哈希的形状了。
今天题目做完自己再来二刷一遍:
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
let ans = [];
nums.sort((a,b)=>a-b);
for(let i=0;i<nums.length;i++){
if(nums[i]>0) break;//如果遍历的当前数字已经大于0了,那么往右边找是不可能找到和他加起来反而等于0的数的
if(i>0&&nums[i] ==nums[i-1]) continue;//要是遍历的数字和他左边的数一样,那就跳过去
let left = i+1,right = nums.length -1;
while(left<right){
let sum =nums[i]+nums[left]+nums[right];
if(sum==0){
ans.push([nums[i],nums[left],nums[right]]);
while(nums[left]==nums[left+1]) left++;//别忘记
while(nums[right]==nums[right-1]) right--;//别忘记
left++;//别忘记
right--;//别忘记
}else if(sum<0){
left++;
}else{
right--;
}
}
}
return ans;
};
18.四数之和
题目链接:18.四数之和
文档讲解:代码随想录/四数之和
视频讲解:视频讲解-四数之和
状态:已完成(1遍)
解题过程
看到题目的第一想法
在上一题的基础上,多一层for循环,先来一次for循环遍历的到除掉次数之外的targetNum,然后再来个for循环步骤和上一题一样。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function (nums, target) {
if (nums.length < 4) return [];
let ans = [];
nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length - 3; i++) {
if (nums[i] > target) break;
if (i > 0 && nums[i] == nums[i - 1]) continue;
let targetNum = target - nums[i];
for (let j = i + 1; j < nums.length-2; j++) {
if (nums[j] > targetNum) break;
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
let left = j + 1, right = nums.length - 1;
while (left < right) {
let sum = nums[j] + nums[left] + nums[right];
if (sum == targetNum) {
ans.push([nums[i], nums[j], nums[left], nums[right]]);
while (nums[left] == nums[left + 1]) left++;
while (nums[right] == nums[right - 1]) right--;//注意先判断是否相等,再去重left和right
left++;
right--;
} else if (sum < targetNum) {
left++;
} else {
right--;
}
}
}
}
return ans;
};
运行没问题,提交竟然只有239/294。
仔细的debug了一下,发现这里不能再像三数之和那样,鲁莽的判定数组最小的值是不是大于0了,最小的数大于0,那怎么加都不可能为0确实不假,但是如果最小的数是一个负数,目标值是一个比数组最小的数还小的负数,这是不能跳过的!多个负数相加会变得越来越小。。。。干!
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function (nums, target) {
if (nums.length < 4) return [];
let ans = [];
nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length - 3; i++) {
// if (nums[i] > target) break;
//这里不能再和三数之和等于0类比,因为两个负数相加会变得更小
if (i > 0 && nums[i] == nums[i - 1]) continue;
let targetNum = target - nums[i];
for (let j = i + 1; j < nums.length-2; j++) {
// if (nums[j] > targetNum) break;
//这里不能再和三数之和等于0类比,因为两个负数相加会变得更小
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
let left = j + 1, right = nums.length - 1;
while (left < right) {
let sum = nums[j] + nums[left] + nums[right];
if (sum == targetNum) {
ans.push([nums[i], nums[j], nums[left], nums[right]]);
while (nums[left] == nums[left + 1]) left++;
while (nums[right] == nums[right - 1]) right--;//注意先判断是否相等,再去重left和right
left++;
right--;
} else if (sum < targetNum) {
left++;
} else {
right--;
}
}
}
}
return ans;
};
这下没问题了。
看完代码随想录之后的想法
这道题目的思路跟我想的大概一致,就是上面那个bug太小丑了,各位看到了就别犯这种错误了哈哈。
总结
哈希了两天之后别的算法在脑子里的印象又减弱了,任重而道远啊同志们,这周休息日的时候好好回顾一下之前的算法题!