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

454.四数相加Ⅱ
map作为哈希表

点击查看代码
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        int result = 0;
        unordered_map<int, int> nums1add2;
        for(int i = 0; i < nums1.size(); ++i) {
            for(int j = 0; j < nums2.size(); ++j) {
                ++nums1add2[nums1[i] + nums2[j]];
            }
        }
        for(int i = 0; i < nums3.size(); ++i) {
            for(int j = 0; j < nums4.size(); ++j) {
                if(nums1add2.find((nums3[i] + nums4[j]) * -1) != nums1add2.end()) {
                    result += nums1add2[(nums3[i] + nums4[j]) * -1];
                }
            }
        }
        return result;
    }
};

核心思想:
本题若使用暴力法遍历nums1 + nums2 + nums3 + nums4,需要O(n4)的时间复杂度,将问题进行转换,变成寻找nums[i] + nums[j] = -(nums[k] + nums[l]),这样就可以将问题拆解为两个O(n2)的时间复杂度。由于本题需要查找符合条件的和值是否存在,故应使用哈希表,本题不仅需要查找符合条件的和值是否存在,存在时,还需要统计一共有几个符合条件的和值,故哈希表需要存储的是(和值,个数)的键值对,所以选取unordered_map作为哈希表。先遍历nums[i] + nums[j],计算和值作为key存储在哈希表中,并累计个数作为value,然后遍历nums[k] + nums[l],查找key值:-(nums[k] + nums[l])是否存在,若存在,则将其value即个数累加到result中,遍历结束后返回result值。

383.赎金信
数组作为哈希表

点击查看代码
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int hash_arr[26] = {0};
        for(int i = 0; i < ransomNote.size(); ++i) {
            ++hash_arr[ransomNote[i] - 'a'];
        }
        for(int i = 0; i < magazine.size(); ++i) {
            --hash_arr[magazine[i] - 'a'];
        }
        for(int i = 0; i < 26; ++i) {
            if(hash_arr[i] > 0) return false;
        }
        return true;
    }
};

本题与242.有效的字母异位词基本一致,区别仅在于:字母异位词最后遍历hash_arr时检查是否有不为0的数,有则return false,而本题检查是否有大于0的数,有则return false。
一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!

15.三数之和(难)
由于对结果去重较为麻烦,哈希表不适用,需要使用双指针法

点击查看代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < nums.size() - 2; ++i) {
            if(nums[i] > 0) return result; //剪枝
            //连续相同元素中处理的一定是第一个元素,处理后才对第一个元素后的相同元素进行去重
            if(i > 0 && nums[i] == nums[i - 1]) continue; //不要漏了i>0这个条件
            //left和right指针原理同最小长度子数组题中的滑动窗口左右边界,均只能往一个方向滑动
            int left = i + 1, right = nums.size() - 1;
            //此while循环能处理完以nums[i]作为三元组中第一个元素的所有情形
            while(left < right) {
                while(left < right && nums[i] + nums[left] + nums[right] > 0) --right;
                while(left < right && nums[i] + nums[left] + nums[right] < 0) ++left;
                if(left < right && nums[i] + nums[left] + nums[right] == 0) {
                    result.push_back({nums[i], nums[left], nums[right]});
                    //处理后才进行去重
                    while(left < right && nums[right] == nums[right - 1]) --right;
                    while(left < right && nums[left] == nums[left + 1]) ++left;
                    --right;
                    ++left;   
                }
            }
        }
        return result;
    }
};

算法中的剪枝指的是在搜索或求解过程中,提前排除一些不必要的计算或路径,从而减少计算量,提高效率。这通常通过某些规则来判断哪些部分的搜索空间不可能得到有意义的结果,从而避免继续对这些部分进行进一步的探索。

既然三数之和可以使用双指针法,之前的1.两数之和,可不可以使用双指针法呢?
如果不能,题意如何更改就可以使用双指针法呢?
两数之和 就不能使用双指针法,因为1.两数之和要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了,所以两数之和只能使用unordered_map作为哈希表。
如果1.两数之和要求返回的是数值的话,就可以使用双指针法了。

18.四数之和(难)
双指针

点击查看代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < nums.size() - 3; ++i) {
            //一级剪枝
            if(nums[i] > target && nums[i] >= 0) return result;
            //对nums[i]去重,注意条件i - 1 >= 0  →  i > 0
            if(i > 0 && nums[i] == nums[i - 1]) continue;
            for(int j = i + 1; j < nums.size() - 2; ++j) {
                //二级剪枝
                if(nums[i] + nums[j] > target && nums[j] >= 0) return result;
                //条件:j - 1 > i,对nums[j]去重
                if(j > i + 1 && nums[j] == nums[j - 1]) continue;
                int left = j + 1, right = nums.size() - 1;
                while(left < right) {
                    while(left < right && nums[i] + nums[j] + nums[left] + nums[right] > target)
                        --right;
                    while(left < right && nums[i] + nums[j] + nums[left] + nums[right] < target)
                        ++left;
                    if(left < right && nums[i] + nums[j] + nums[left] + nums[right] == target) {
                        result.push_back({nums[i], nums[j], nums[left], nums[right]});
                        //处理后才进行去重
                        while(left < right && nums[right] == nums[right - 1]) --right;
                        while(left < right && nums[left] == nums[left + 1]) ++left;
                        --right;
                        ++left;
                    }
                }
            }
        }
        return result;        
    }
};

本题相对于15.三数之和只是多加了一层循环,多加了一级剪枝和去重而已,其他操作完全一致

1.两数之和与454.四数相加Ⅱ为同一题型,使用unordered_map作为哈希表
15.三数之和与18.四数之和为同一题型,哈希表不适用,使用双指针,且可以扩展至五数之和,n数之和...,只要最后两个数使用双指针寻找,前面的数进行循环固定即可

2025/02/19

posted @   coder小杰  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示