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

文章介绍了如何使用哈希表和双指针技巧解决四数之和问题,以及类似的两数之和和字符串构造问题,通过预处理和剪枝优化算法效率。
摘要由CSDN通过智能技术生成

454 四数相加II

  • 题意

    • 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
    • 为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。
      • 例如:
        • 输入: A = [ 1, 2],B = [-2,-1],C = [-1, 2],D = [ 0, 2]
        • 输出: 2
        • 解释:
          • 两个元组如下:
            • 1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
            • 2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
  • 思路

    • 将四个数的和分成两个组的和,即可视为两数之和同类型的题目;已知a+b的值,判断c+d有target-(a+b)的值,判断一个值有没有在集合中出现过->使用哈希表;
    • 最终需要统计出现的次数(可以用下标来统计),因此不仅需要数的和value,更需要下标的key值;因此选择map;
    • 最终的count计数:count+=value;而不是count++;因为符合条件的在集合内的a+b的值不只是一个,而是这个值出现的次数;
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> map;
        int count = 0;
        for (int n1 : nums1) {//key:a + b的数值,value : a + b数值出现的次数
            for (int n2 : nums2) {
                map[n1 + n2]++;
            }
        }
        for (int n3 : nums3) {
            for (int n4 : nums4) {
                if (map.find(0 - (n3 + n4)) != map.end())//找到了n3和n4对应的value
                    count += map[0 - (n3 + n4)];
            }
        }
        return count;
    }
};

383 赎金信

  • 题意

    • 给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
    • (题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)
    • 示例:
      • 你可以假设两个字符串均只含有小写字母。
      • canConstruct("a", "b") -> false
      • canConstruct("aa", "ab") -> false
      • canConstruct("aa", "aab") -> true
  • 思路

    • 暴力双循环:
      • 两层遍历从magazine中删减对应的ransom的字母,如果ransom被删完了说明magazine全覆盖;
    • 哈希表解法:
      • 用一个长度为26的数组记录magazine里字母出现的次数,然后用这个数组来减去ransom中出现的次数更新数组,如果数组的某个value小于零,说明未能全覆盖;(类比有效的字母异位词)
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = { 0 };
        //先将magazine中的值加入到record中计数
        for (int i = 0; i < magazine.length(); i++) {
            record[magazine[i] - 'a']++;
        }
        for (int i = 0; i < ransomNote.length(); i++) {
            record[ransomNote[i] - 'a']--;
            if (record[ransomNote[i] - 'a'] < 0) return false;//说明magazine中的值不够ransomNote减的
        }
        //遍历完均为出现异常,则返回true
        return true;
    }
};

 15 三数之和

  • 题意

    • 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
      • 注意: 答案中不可以包含重复的三元组。
    • 示例:
      • 给定数组 nums = [-1, 0, 1, 2, -1, -4],
      • 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
  • 思路

    • 先使用排序对数组进行排序操作,使之从小到大排序
    • 双指针思路:对数组进行遍历,对每一个当前数字,使用左指针指向当前数字的下一个,右指针指向尾部数字;nums[i] + nums[left] + nums[right] > 0,右指针左移;nums[i] + nums[left] + nums[right] <0,左指针右移,直到等于0;
    • 去重操作:
      • 对于遍历的每一个指针去重,i>0 && nums[i]==nums[i-1].continue;对i进行去重;
      • 对于左指针和右指针去重:while (right > left && nums[left] == nums[left + 1]) left++;注意始终要加right>left的限制条件;right同理。
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>>result;//创建一个二维数组
        int left, right;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > 0) return result;//排序完第一个数就是正数,直接GG,返回result;
            //去重操作1,对i进行去重
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            //初始化双指针
            left = i + 1;
            right = nums.size() - 1;
            while (right > left) {//三个数,nums[i],nums[left],nums[right],所以不能取等号
                //双指针收缩寻找适合的三元素
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if (nums[i] + nums[left] + nums[right] < 0) left++;
                //在确定有一组正解之后,移动right和left进行对left和right进行去重
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    while (right > left && nums[left] == nums[left + 1]) left++;//left左移,nums[left]的值不变
                    while (right > left && nums[right] == nums[right - 1]) right--;//right右移,nums[right]的值不变
                    //找到了i对应的三要素且对left,right去重之后,双指针同时收缩,寻找当前i所对应的下一组left和right
                    right--;
                    left++;
                    //right和left收缩放在里面而不是外面,以为无论有没有找到合适的三要素,if ,else if,else对应的条件都会使得双指针收缩
                }
            }
        }
        return result;
    }
};

 18 四数之和

  • 题意

    • 给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
      • 注意:答案中不可以包含重复的四元组。
    • 示例: 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
  • 思路

    • 双指针法,在三数之和的基础山再套一层for循环
    • 排序:先排序,将数组按照从小到大的顺序排列
    • 剪枝操作:算法优化,通过某种判断,避免不必要的搜索遍历;
      • 一层剪枝:考虑到target可正可负,不能直接nums[k]>target(nums[k]>target && nums[k]>0)
      • 二层剪枝:对nums[k]+nums[i]的和进行剪枝操作即nums[k]+nums[i]>target && nums[i]>0;
    • 去重操作:考虑到题目要求四元组不重复,因此也需要像三数之和一样进行去重操作;
      • 一层去重:if(k>0 && nums[k]==nums[k-1]);
      • 二层去重:if(i>k+1 && nums[i]==nums[i-1]);
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>>result;
        //先排序
        sort(nums.begin(), nums.end());
        //k < nums.size() - 3 和 i < nums.size() - 2会报错:nums.size()的返回值类型为无符号整数
        for (int i = 0; i < nums.size(); i++) {
            //第一层剪枝(对i)
            //if (target > 0 && nums[i] > 0 && nums[i] > target)break;
            //剪枝可进一步优化为如下,因为nums[i]之后的都是比nums[i]大,加起来肯定比target大,所以直接break终止当前的循环,后面的都不需要遍历了
            if (nums[i] > 0 && nums[i] > target) break;
            //第一层去重,参考三数之和
            if (i > 0 && nums[i] == nums[i - 1]) continue;//跳过当前i,快进到下一个i;
            for (int k = i + 1; k < nums.size(); k++) {
                //第二层剪枝(对nums[i]和nums[k]的和)
                //if (nums[k] + nums[i] >= 0 && nums[i] + nums[k] > target) break;
                //优化如下:
                if (nums[k] + nums[i] > target && nums[i] > 0)break;
                //第二层去重
                if (k > i+1 && nums[k] == nums[k - 1]) continue;
                int left = k + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    //long扩充int,防止nums =[0, 0, 0, 1000000000, 1000000000, 1000000000, 1000000000]这样的变态数据
                    if ((long)nums[i] + nums[k] + nums[left] + nums[right] > target) right--;
                    else if ((long)nums[i] + nums[k] + nums[left] + nums[right] < target) left++;
                    else {
                        result.push_back(vector<int>{ nums[i],nums[k],nums[left],nums[right] });
                        //对right和left去重
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1])left++;
                        right--;
                        left++;
                    }
                }
            }
        }
        return result;
    }
};

总结 

2024.1.2补的第二篇,哈希表结束,明天把字符串的两篇给补了~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值