代码随想录算法训练营第七天| 454.四数相加 II、383.赎金信、 15.三数之和、18.四数之和

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太小丑了,各位看到了就别犯这种错误了哈哈。

总结

哈希了两天之后别的算法在脑子里的印象又减弱了,任重而道远啊同志们,这周休息日的时候好好回顾一下之前的算法题!

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值