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

LeetCode 454.四数相加II

题目链接🔗:454.四数相加II

解题思路🤔

其实这道题完全可以看做是稍有变动的1. 两数之和,即把A[i] + B[j] + C[k] + D[l] = 0看作(A[i] + B[j]) + (C[k] + D[l]) = 0。因此,思路就很好想了。

还是那四个问题:

  • 为什么会想到用哈希表
    • 当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法
  • 哈希表为什么用map
    • 题目要求找到两个不同的目标,k-v键值对更易于保存
  • 本题map是用来存什么的
    • 存放遍历过的元素
  • map中的key和value用来存什么的
    • key:A[i] + B[j]的和,value:A[i] + B[j]的和出现的次数

重点分析一下key与value的存放:

  1. 在对比时的判断条件是(0 - (A[k] + B[l])) 在map中是否出现过(判断(0 - (A[i] + B[j])) 在map中是否出现当然也可以,这里是为了方便选择了前者),因此用于对比的key就使用(A[i] + B[j])
  2. 题目中要求返回符合条件的元组有多少个,我们定义一个变量count来统计,只要出现(A[i] + B[j]) + (C[k] + D[l]) = 0,即(0 - (C[k] + D[l])) 在map中出现过,就让count + 1,最后返回count即可。因此,value的值就是A[i] + B[j]的和出现的次数。

遇到的问题😢

代码实现👨🏻‍💻

第一种方法

var fourSumCount = function(nums1, nums2, nums3, nums4) {
    const map = new Map(); // 定义Map结构

    for (const i of nums1) { // 遍历nums1、nums2,这里i与j就代表着nums[i]、nums[j]
        for (const j of nums2) {
        	// 将i + j的和放入key,出现此处放入value,get()获取map的value值,出现就将其加入,未出现则加入0。
        	// +1用来保证第一次获取value时添加的就是1,第二次循环get()返回的是1,再+1就变为2,以此类推
            map.set(i + j, (map.get(i + j) || 0) + 1);
        }
    }

    let count = 0;

    for (const k of nums3) { // 遍历nums3、nums4,这里k与l就代表着nums[k]、nums[l]
        for (const l of nums4) {
            if (map.has(0 - (k + l))) { // 判断map中是否存在0 - (k + l) = i+ j
                count += (map.get(0 - (k + l)) || 0); // 如果存在就让count + 1
            }
        }
    }

    return count;
};

总结📖

第一种方法

有了1. 两数之和的基础后这道题并不难想,主要注意在向map中添加(A[i] + B[j]时要注意value如何添加。


LeetCode 383. 赎金信

题目链接🔗:383. 赎金信

解题思路🤔

整体思路有点像242.有效的字母异位词,采用哈希法,用长度为26的数组来记录magazine串中出现的字母,再用ransomNote对比,判断数组中是否包含了能组成randomNote串的字母。

遇到的问题😢

代码实现👨🏻‍💻

哈希表法

var canConstruct = function(ransomNote, magazine) {
    const arr = new Array(26).fill(0); // 定义数组
    const k = "a".charCodeAt(); // 记录a的ASCII码,用于计算与串中对应字符的ASCII码的差值

    // 记录magazine串中字母出现的次数,出现则在对应位置+1
    for (const i of magazine) arr[i.charCodeAt() - k]++;
    // 与ransomNote对比
    for (const j of ransomNote) {
        const index = j.charCodeAt() - k; // 定义index存放ransomNote串中每个字符与k的ASCII码的差值
        if (!arr[index]) return false; // 未匹配成功,返回false
        arr[index]--; // 匹配成功,减去数组中对应的字符出现的次数
    }
    return true;
};

总结📖

哈希表法

注意对数据结构的选择,数组更节约资源。


LeetCode 15. 三数之和

题目链接🔗:15. 三数之和

解题思路🤔

三个互不相同的元素相加后的结果为0。总结一下卡哥的题解:

  1. 首先排序数组为非递减数组,然后定义指针i指向下标0,指针left指向i + 1,指针right指向末尾元素
  2. 要完成的任务变为nums[i] + nums[left] + nums[right] = 0
  3. for循环让i遍历数组,如果nums[i] + nums[left] + nums[right] > 0说明,三数之和过大了,right指针要向左移动,让三数之和变小一点;
  4. 如果nums[i] + nums[left] + nums[right] < 0说明,三数之和过小了,left指针要向右移动,让三数之和变大一点。
  5. 直到leftright相遇,此时i进行下一次移动,left回到当前的i + 1位置,重复寻找操作。
  6. 找到的元组存在重复的可能。因此还要进行去重,去重操作要完成的是不能有重复的三元组,但三元组内的元素是可以重复的,要判断的是nums[i]nums[i - 1]是否重复,这样才能避免将三元组内的重复元素也给排除了这一问题

遇到的问题😢

尝试了哈希表写法,非常麻烦且容易漏细节,调了十分钟不行,直接去看了卡哥题解,后面使用双指针做出来了。

代码实现👨🏻‍💻

双指针法

var threeSum = function(nums) {
    const res = [];
    const len = nums.length;

    // 排序数组
    nums.sort((a, b) => a - b );

    for(let i = 0; i < len; i++) {
        let left = i + 1, right = len - 1,

        if (nums[i] > 0) return res; // 非递减数组,第一个元素大于0,直接返回res
        if (nums[i] == nums[i - 1]) continue; // 去重操作,避免将三元组内的重复元素也给排除了

        while(left < right) {
            let sum = nums[left] + nums[i] + nums[right];
            
            if(sum < 0) left++; // 三数之和过小了,left右移
            else if(sum > 0) right--; // 三数之和过大了,right左移
            else {
                res.push([nums[i], nums[left], nums[right]]); // 找到三数之和 = 0,向res中插入结果
                while(left < right && nums[left] == nums[left + 1]) left++; // 去重,避免将三元组内的重复元素也给排除了
                while(left < right && nums[right] == nums[right - 1]) right--; // 去重,避免将三元组内的重复元素也给排除了
                left++; // 插入后继续移动left寻找下一个的三数之和
                right--; // 插入后继续移动right寻找下一个的三数之和
            }
        }
    }
    return res;
};

总结📖

双指针法


LeetCode 18. 四数之和

题目链接🔗:18. 四数之和

解题思路🤔

整体思路与三数之和一样,不同之处在于要比三数之和多处理一次,两层for循环找到两个值,然后leftright指针遍历找到剩下两个值。

遇到的问题😢

代码实现👨🏻‍💻

双指针法

var fourSum = function(nums, target) {
    const len = nums.length;
    
    if(len < 4) return []; // 数组长度小于4,直接返回
    nums.sort((a, b) => a - b); // 排序数组
    const res = [];

    for(let i = 0; i < len - 3; i++) { // 第一次缩小范围
        if(i > 0 && nums[i] === nums[i - 1]) continue; // 去重
        for(let j = i + 1; j < len - 2; j++) { // 第二次缩小范围
            if(j > i + 1 && nums[j] === nums[j - 1]) continue; // 去重
            
            let left = j + 1let right = len - 1;
                
            while (left < right) {
                const sum = nums[i] + nums[j] + nums[left] + nums[right];
                if(sum < target) { // 四数之和小于target,移动left指针寻找更大的数
                    left++;
                    continue;
                } else if (sum > target){ // 四数之和大于target,移动right指针寻找更小的数
					right--;
                    continue;
				} else {
					res.push([nums[i], nums[j], nums[left], nums[right]]); // 向res中插入结果
					// 去重
                	while (left < right && nums[left] === nums[left + 1]) left++;
                	while (left < right && nums[right] === nums[right - 1]) right--;

                	left++; // 插入成功后继续移动left寻找下一个的四数之和
                	right--; // 插入成功后继续移动left寻找下一个的四数之和
				} 
            }
        }
    }
    return res;
};

总结📖

双指针法

多了一次一层判断的情况下,也要记得对增加的判断进行去重。


今日收获

  1. 十分钟之内想不出来思路/改不出来代码就可以看题解了,没必要浪费太多时间钻牛角尖。一刷学思路,二刷搞巩固。
  2. 思路要灵活,不能学了哈希法忘了双指针。。。
  3. 感觉时间不太够用了,上班少摸鱼多做题!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值