✅✅代码随想录算法训练营Day7 | | 454.四数相加II,383. 赎金信,15. 三数之和 ,18. 四数之和

161 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第10篇文章 点击查看文章详情 🚀🚀

454. 四数相加 II - 力扣(LeetCode)

难度:🔥🔥🔥

image.png

Map实现

var fourSumCount = function(nums1, nums2, nums3, nums4) {
    let resmap = new Map();
    for(let i of nums1){
        for(let j of nums2){
            let sum = i + j;
            resmap.set(sum,(resmap.get(sum) || 0) + 1);
        }
    }
    let count = 0;
    for(let i of nums3){
        for(let j of nums4){
            let sum = i + j;
            count = count + (resmap.get(0-sum) || 0);
        }
    }

    return count
};

难点

几乎所有的求和问题,都可以转化为求差问题

当然这道题也不例外

因为求差问题,都是两个数相减。

但这里有四个数,怎么办?

那我们就两两合并就好了~

大体思路如下:

  1. 首先定义一个mapkey放a和b两数之和,value 放a和b两数之和出现的次数。
  2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
  3. 创建变量count,用来统计 a+b+c+d = 0 出现的次数。
  4. 在遍历大C和大D数组,找到如果 0-(c+d)map中出现过的话,就用countmap中key对应的value也就是出现次数统计出来。
  5. 最后返回统计值 count 就可以了

为什么用map不用set?

其实也不难

map:是一组键值对的结构,和JSON对象类似。

set:类似于数组,且成员的值都是唯一的

而在这里,我们需要用到map中的键值对,所以只能用map

383. 赎金信 - 力扣(LeetCode)

难度:🔥

image.png

创建map对象

var canConstruct = function(ransomNote, magazine) {
    if(ransomNote.length > magazine.length) return false;

    let map = { }
    for(const i of magazine){
        map[i] = (map[i] || 0) + 1;
    }

    for(const i of ransomNote){
        if(map[i]){
            map[i]--;
            if(map[i] < 0)
                return false;
        }
        else{
            return false
        }
    }
    return true;
};

难点

哈哈哈哈,这个题没有难度了,和昨天的有效字母异位词差不多,第一遍就拿下了😁😁😁

15. 三数之和 - 力扣(LeetCode)

难度:🔥🔥🔥🔥

image.png

双指针实现

const threeSum = function(nums) { 
// 用于存放结果数组 
let res = [] // 给 nums 排序
nums = nums.sort((a,b)=>{ return a-b }) 
// 缓存数组长度 
const len = nums.length 
// 注意我们遍历到倒数第三个数就足够了,因为左右指针会遍历后面两个数
for(let i=0;i<len-2;i++) { 

    // 左指针j 
    let j=i+1 
    // 右指针k 
    let k=len-1 
    // 如果遇到重复的数字,则跳过
    if(i>0&&nums[i]===nums[i-1]) { 
        continue 
    } 
    while(j<k) { 
        // 三数之和小于0,左指针前进
        if(nums[i]+nums[j]+nums[k]<0){ 
            j++
            // 处理左指针元素重复的情况
            while(j<k&&nums[j]===nums[j-1]) { j++ } 
            } 
        else if(nums[i]+nums[j]+nums[k]>0){
            // 三数之和大于0,右指针后退 k-- 
            // 处理右指针元素重复的情况 
            while(j<k&&nums[k]===nums[k+1]) { k-- } 
        } 
        else { 
            // 得到目标数字组合,推入结果数组 
            res.push([nums[i],nums[j],nums[k]]) 
            // 左右指针一起前进 
            j++ 
            k-- 
            // 若左指针元素重复,跳过
            while(j<k&&nums[j]===nums[j-1]) { j++ } 
            // 若右指针元素重复,跳过 while(j<k&&nums[k]===nums[k+1]) { k-- }
        } 
    } 
   } 
    // 返回结果数组
    return res 
};

难点

实现思路

修言老师讲解肯定比我好得多,所以直接上大佬的吧~

三数之和延续两数之和的思路,我们可以把求和问题变成求差问题——固定其中一个数,在剩下的数中寻找是否有两个数和这个固定数相加是等于0的。

虽然乍一看似乎还是需要三层循环才能解决的样子,不过现在我们有了双指针法,定位效率将会被大大提升,从此告别过度循环~

(这里大家相信已经能察觉出来双指针法的使用场景了,一方面,它可以做到空间换时间;另一方面,它也可以帮我们降低问题的复杂度。)

双指针法用在涉及求和、比大小类的数组题目里时,大前提往往是:该数组必须有序。否则双指针根本无法帮助我们缩小定位的范围,压根没有意义。因此这道题的第一步是将数组排序:

nums = nums.sort((a,b)=>{ return a-b })

然后,对数组进行遍历,每次遍历到哪个数字,就固定哪个数字。然后把左指针指向该数字后面一个坑里的数字,把右指针指向数组末尾,让左右指针从起点开始,向中间前进:

image.png

每次指针移动一次位置,就计算一下两个指针指向数字之和加上固定的那个数之后,是否等于0。如果是,那么我们就得到了一个目标组合;否则,分两种情况来看:

  • 相加之和大于0,说明右侧的数偏大了,右指针左移
  • 相加之和小于0,说明左侧的数偏小了,左指针右移

tips:这个数组在题目中要求了“不重复的三元组”,因此我们还需要做一个重复元素的跳过处理。这一点在编码实现环节大家会注意到。

为什么要先排序

因为我们的思路是,通过移动两个双指针,判断它们三者的值是否满足条件,如果两个指针区间内值是无序的,我们就不能进行如下操作:

  • 三者相加之和大于0,说明右侧的数偏大了,右指针左移
  • 三者相加之和小于0,说明左侧的数偏小了,左指针右移

剪枝+去重

  • 剪枝:为什么nums[i]===nums[i-1],而不是nums[i]===nums[i+1]?

    首先这里的i是我们要固定的那个数,在那个数后面紧挨着的就是双指针的left了。

    如果用了nums[i]===nums[i+1]的话,[-1,-1,2]这个答案,就会当i等于第一个-1的时候,直接因为第二个数还是-1就舍去了。

  • 去重:在循环中,对每种情况都要进行判断
 while(j<k) { 
        // 三数之和小于0,左指针前进
        if(nums[i]+nums[j]+nums[k]<0){ 
            j++
            // 处理左指针元素重复的情况
            while(j<k&&nums[j]===nums[j-1]) { j++ } 
            } 
        else if(nums[i]+nums[j]+nums[k]>0){
            // 三数之和大于0,右指针后退 k-- 
            // 处理右指针元素重复的情况 
            while(j<k&&nums[k]===nums[k+1]) { k-- } 
        } 
        else { 
            // 得到目标数字组合,推入结果数组 
            res.push([nums[i],nums[j],nums[k]]) 
            // 左右指针一起前进 
            j++ 
            k-- 
            // 若左指针元素重复,跳过
            while(j<k&&nums[j]===nums[j-1]) { j++ } 
            // 若右指针元素重复,跳过 while(j<k&&nums[k]===nums[k+1]) { k-- }
        } 
    } 

18. 四数之和 - 力扣(LeetCode)

难度:🔥🔥🔥🔥

image.png

双指针

var fourSum = function(nums, target) {
    let len = nums.length;
    let res = []
    nums.sort((a,b) => a - b)
    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 l = j + 1;
            let r = len-1;
            while(l < r){
                let sum = nums[i] + nums[j] + nums[l] + nums[r];
                if(sum > target){
                    r--;
                    while(l < r && nums[r] == nums[r+1]){
                        r--
                    }
                }
                else if(sum < target){
                    l++;
                    while( l < r && nums[l] == nums[l-1]){
                        l++;
                    }
                }
                else{
                    res.push([nums[i],nums[j],nums[l],nums[r]])
                    l++
                    r--;
                    while((l < r) && nums[r] == nums[r+1]){
                        r--;
                    }
                    while(l < r && nums[l] == nums[l-1]){
                        l++;
                    }
                }
            }
        }

    }
    return res;
};

这里和三数之和的思路基本上一样,唯一的区别就是要再多一层循环。

ij的和合并,对后面两个数再使用双指针.

收获

今天的题目比昨天的要难一些,但收获也更多!

  • 四数相加:两两合并的思路挺有意思的,以及熟悉了一下map这个数据结构的api

    map.set

    map.get

  • 赎金信:和昨天的题一样,收获已经在昨天完成了~
  • 三数之和:不愧是经典题型

    先排序再进行双指针的移动

    怎么样剪枝和去重

  • 四数之和:是三数之和的升级版,但也就多了一层for循环而已~

⭐⭐⭐