【代码随想录 | day07】(JavaScript) 哈希表第二弹:四数相加II、赎金信、三数之和、四数之和

该博客内容参考Carl老师的《代码随想录》,记录一些自己的所思所想,以及卡神的解题思路。涉及题目:四数相加II、赎金信、三数之和、四数之和

454.四数相加II

题目链接:454. 四数相加 II - 力扣(LeetCode)

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

本题是哈希表的一道经典题目

  • HashMap存两个数组之和,如AB。然后计算两个数组之和,如 CD。时间复杂度为:O(n^2)+O(n^2),得到 O(n^2).
  • 将四个数组分为两块通过map记录A + B的值,及出现的次数(用 val 存起来);
  • 在遍历C和D时,判断0 - (C + D)是否在map中存在;

这道题应该思考的重点就是,遇到这种题目为什么会想到用哈希表。(需要查询元素)

代码
var fourSumCount = function(nums1, nums2, nums3, nums4) {
    const twoSumMap = new Map();
    let count = 0;

     // 通过map记录nums1 + nums2的值
    for(const n1 of nums1) {
        for(const n2 of nums2) {
            const sum = n1 + n2;
            twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1)
        }
    }
	
    // 判断 0 - (nums1 + nums2)是否在map中存在
    for(const n3 of nums3) {
        for(const n4 of nums4) {
            const sum = n3 + n4;
            count += (twoSumMap.get(0 - sum) || 0)
        }
    }

    return count;
};

借用代码示例中的数据来解释一二。

nums1 = [1, 2], nums2 = [-2, -1], nums3 = [-1, 2], nums4 = [0, 2]

  • twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1)
    解读: 刚开始 sum = 1 + (-2) 时, 存的键为两数之和 -1 ,值代表了这个和出现的次数,这样之和需要查询 key 时,就知道了出现了几次。
    补充一点

    map1.set('bar');
    console.log(map1.get('bar'));  // undefined 
    console.log((map1.get('bar') || 0) + 1);  // 值为1  其中 (undefined || 0) 值为0
    

    set()方法Map 对象添加或更新一个指定了键(key)和值(value)的(新)键值对。

  • twoSumMap,即 Map(3) { -1 => 1, 0 => 2, 1 => 1 }

  • 第二个双重 for 循环中,count += (twoSumMap.get(0 - sum) || 0)是什么意思?
    解读:已知 count 初始值为 0,当能找到0-(nums3 + nums4)时,自然要看符合条件的两数之和有几个。恰好哈希表里面已经统计好了,只有在循环的过程中不断更新 count 的值就可以了。


383.赎金信

题目链接:383. 赎金信 - 力扣(LeetCode)

注意:

  • magazine 中的每个字符只能在 ransomNote 中使用一次
  • ransomNotemagazine 由小写英文字母组成

这道题目和242.有效的字母异位词 (opens new window)很像,有效的字母异位词 相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。

这一题不推荐使用双重for循环的暴力解法,时间复杂度比较高,而且里面还有一个字符串删除也是费时的。

由于字母全是小写的,考虑用哈希解法,用空间换时间效率。这里得回顾一下,怎么将26个字母变成哈希表了—— const resSet = new Array(26).fill(0);这里采用数组而不是map,

要注意的是,将 magazine 的字母存到哈希表中。

代码
var canConstruct = function(ransomNote, magazine) {
    const resSet = new Array(26).fill(0);
    const base = "a".charCodeAt(); // 将a的下标处理成0
    for(const i of magazine) {
        // index为当前字母所对应的下标
        let index = i.charCodeAt() - base;
        resSet[index]++;
    }
    // console.log(resSet)
    for(const i of ransomNote) {
        // index为当前字母所对应的下标
        let index = i.charCodeAt() - base;
        // 如果当前下标中元素为0
        if(!resSet[index]) return false
        resSet[index]--
    }
    return true
};

关于代码的解读,可以参考我之前文章中对有效字母异位词那题的总结。链接:
【代码随想录 | day05】(JavaScript) 哈希表理论基础以及相关算法题


15.三数之和

题目链接:15. 三数之和 - 力扣(LeetCode)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

这道题相对于两数之和,复杂之处就在于要去重。我们要求的是不能重复的三元组,但是三元组里面的元素是可以重复的。

  • a, b ,c, 对应的就是 nums[i],nums[left],nums[right]。
  • 考虑 a 的去重时,应该判断 nums[i] 与 nums[i-1] 是否相同。因为当 i 的下一个元素值和 i 相等时,不会被 pass 掉。不能有重复的三元组,但三元组内的元素是可以重复的!
  • 考虑 b 和 c 的去重时,为了避免取到结果[0, -1, 1]之后,left又遇到-1,right又遇到1。所以还需要判断一下。
代码

先排序,然后定义指针。考虑排序后的第一个数是否大于 0 。对 a 去重

var threeSum = function(nums) {
    let result = [], len = nums.length;;
    // 先给数组排序
    nums.sort((a, b) => a-b)
    console.log(nums)
    
    for(let i = 0; i < len; i++) {
        // 双指针
        let left = i + 1, right = len - 1, a = nums[i];  
        
        // 排序后的第一个数大于0,则所以数都大于0
        if(a > 0) return result;

        // 去重
        if (a == nums[i - 1]) continue

        while(left < right) {
            let b = nums[left], c = nums[right];
            let sum = a + b + c;

            if (sum > 0) right--;
            else if (sum < 0) left++;
            else{
                result.push([a, b, c])
                // 去重
                while(left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                while(left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                // 取到一个结果后,继续让指针移动
                left++;
                right--;
            }
        }
    }

    return result
};

18.四数之和

题目链接:18. 四数之和 - 力扣(LeetCode)

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

代码
var fourSum = function(nums, target) {
    const len = nums.length;
    // 首先考虑数组长度小于4的情况
    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]]);
                while(l < r && nums[l] === nums[++l]);
                while(l < r && nums[r] === nums[--r]);
            }
        } 
    }
    return res;
};

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值