LeetCode|哈希表| 454.四数相加II 、383. 赎金信 、15. 三数之和 、18. 四数之和

目录

一、454.四数相加II 

1.解题思路

2.代码实现

二、 383. 赎金信

1.暴力求解

2.哈希解法

三、15. 三数之和 

1.解题思路(双指针法)

2.代码实现(双指针法)

 四、18. 四数之和

1.解题思路(双指针法)

2.代码实现(双指针法)

 


一、454.四数相加II 

1.解题思路

        题目给了四个数组,要求从每个数组中选取一个元素,然后四个元素相加,计算有多少组四数之和为0。

        暴力求解:直接上四个for循环,遍历四数之和为0,然后计数器++,时间复杂度为O(n^4)。

        哈希表求解:利用unordered_map哈希表结构,将前两个数组两两之和(a+b)的各种可能保存到umap(unordered_map)中,(注意,这里key值为a + b,value值为a+b两数之和出现的次数),然后再遍历后面两个数组,只要从umap中寻找是否有值为“0 - (c + d)”,如果查找到了,就将value值加到计数器里。时间复杂度为O(n^2)

2.代码实现

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        //先遍历前两个数组,将元素之和存放到unordered_map
        unordered_map<int,int> umap;
        for(int a : nums1){
            for(int b : nums2){
                umap[a+b]++;//记录a+b出现了多少次,
            }
        }
        
        //开始遍历后两个数组,然后在unorder_map中查找 0-(c+d)
        int count = 0;//count用来记录出现了多少次 0-(c+d)
        for(int c : nums3){
            for(int d : nums4){
                int target = 0 - (c + d);
                count += umap[target];
            }
        }
        return count;
    }
};

二、 383. 赎金信

1.暴力求解

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        //暴力算法
        //两个for循环遍历,判断是否magazine中有ransomNote中的字符
        //这里需要注意的两个for循环顺序
        for(int i = 0;i < magazine.size();i++){
            for(int j = 0;j < ransomNote.size();j++){
                //如果在ransomNote中找到和magazine相同的字符,就删除掉(因为不能重复使用magazine中字符)
                if(ransomNote[j] == magazine[i]){
                    ransomNote.erase(ransomNote.begin() + j);
                    //既然已经删除了,就break跳出本轮循环
                    break;
                }
            }
        }
        //如果最后ransomNote中没有字符了,说明magazine中包含其所有的字符
        if(ransomNote.length() == 0){
            return true;
        }
        else
            return false;
    }
};

2.哈希解法

         分析:字符串中都是小写英文字母,只有26个且有序,所以可以用数组来记录。将magazine中的字符出现次数记录,然后ransomNote中的字符在这个数组中对应做减减操作,如果这个字符次数<0,说明magazine中的字符不够组成救赎信中的字符。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};//本题字符串中为小写英文字母,可以用哈希数组
        //将magazine中的字符映射到数组中
        for(int i = 0;i < magazine.length();i++){
            record[magazine[i] - 'a']++;
        }
        //因为magazine中字符只能用一次,ransomNote中的字符在数组中对应--,
        for(int  j = 0;j < ransomNote.length();j++){
            record[ransomNote[j] - 'a']--;
        //如果出现负数,说明magazine中的字符不够组成ransomNote
            if(record[ransomNote[j] - 'a'] < 0)
                return false;
        }
        return true;
    }
};

三、15. 三数之和 

1.解题思路(双指针法)

        ①先将数组排序(升序),定义一个二维数组,用来存放三元组结果集。

        ② for循环遍历,i表示第一个数,然后定义双指针left,right。left初始化为i的后一个元素,right初始化为数组最后一个元素。

        ③进行剪枝操作。如果nums[i]的值>0,而此数组又是升序排列,所以left和right肯定也是大于0,此时数组中不可能存在三元组使得三数之和=0,直接return结果集就行。

        ④进行去重操作。题目要求三元组不能重复,所以这里判断一下:

if(i > 0 && nums[i] == nums[i - 1])
    continue;

需要注意的是,这里对下标为i-1的元素进行操作,所以判断语句中要对i的值进行判断:i > 0。

还需要注意,这里不能写成nums[i] == nums[i + 1]。如果这样写,对于-1,-1,2这种情况就忽略掉了。

        ⑤如果三数之和大于0,此时right指向的值最大,就进行right--操作;如果三数之和小于0,就进行left++操作;如果三数之和等于0,则将三元组存放到结果集中。

        ⑥还需要对left和right进行去重操作。去重结束后,左右指针分别++--,寻找以i为第一个数的下一个三元组,最后返回结果集

2.代码实现(双指针法)

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;//定义一个二维数组,用来存放结果集
        //一定要先将nums数组排序
        sort(nums.begin(),nums.end());
        for(int i = 0;i < nums.size();i++){
            //数组目前是升序,如果第一个数就大于0了,那么后面不可能再出现三数之和等于0的三元组
            if(nums[i] > 0)
                return result;
            //去重操作
            if(i > 0 && nums[i] == nums[i - 1]){
                //注意这里不能写nums[i] == nums[i + 1],这样写会漏掉 -1,-1,2的情况
                continue;//还要注意这里的判断条件里面要加 i > 0
            }
            //定义双指针,left代表第二个数,right代表第三个数,i代表第一个数
            int left = i + 1;
            int right = nums.size() - 1;
            while(left < right){//题目要求三个数的下标不相同,所以这里不能写left <= right
                if(nums[i] + nums[left] + nums[right] > 0){//要将三数之和变小,就是将right往左移
                    right--;
                }
                else if(nums[i] + nums[left] + nums[right] < 0){//要将三数之和变大,就是将left右移
                    left++;
                }
                else{//此时三数之和 = 0
                    //存放到结果集result中
                    result.push_back(vector<int>{nums[i],nums[left],nums[right]});
                    //此时找到了一组符合题目要求的三元组,开始寻找以这个i开头的,其他三元组
                    //先去重
                    while(left < right && nums[left] == nums[left + 1])
                        left++;
                    while(left < right && nums[right] == nums[right - 1])
                        right--;
                    //去重之后,双指针分别进行++--操作
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
};

 四、18. 四数之和

1.解题思路(双指针法)

        本题和“15. 三数之和 ”有异曲同工之处,在三数之和的基础上,外面再套一个for循环。但是其中也是有几点需要注意的:

        ①在一级和二级剪枝操作时,不可以像三数之和一样如果第一个数大于target,就直接返回。因为这里target不是三数之和里的0,如果为负数,这样写就错误了。

        因此,在这做剪枝操作时,要加一个前提条件,nums[k] > 0。

        ②去重操作要记得加防止数组越界的条件

        ③判断四数之和的时候,强制转化成长整型long,不然数值溢出了。

2.代码实现(双指针法)

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;//存放四元组结果集
        sort(nums.begin(),nums.end());//先将原数组排序(升序)
        for(int k = 0;k < nums.size();k++){
            //一级剪枝,这里注意target可能是负数,不能像三元组那样剪枝
            if(nums[k] > target && nums[k] > 0)
                break;
            //一级去重,注意这里下标越界和如何如何去重
            if(k > 0 && nums[k] == nums[k - 1])
                continue;
            for(int i = k + 1;i < nums.size();i++){
                //二级剪枝
                if(nums[k] + nums[i] > target && nums[k] + nums[i] > 0)
                    break;
                //二级去重
                if(i > k + 1 && nums[i] == nums[i - 1])
                    continue;
                //定义双指针
                int left = i + 1;
                int right = nums.size() - 1;
                while(left < right){
                    if((long)nums[k] + nums[i] + nums[left] + nums[right] > target)
                        right--;
                    else if((long)nums[k] + nums[i] + nums[left] + nums[right] < target)
                            left++;
                        else {//此时四数之和=target,存放到结果集,并寻找以k,i开头的其他四元组
                            result.push_back(vector<int>{nums[k],nums[i],nums[left],nums[right]});
                            //对left和right进行去重
                            while(left < right && nums[left] == nums[left + 1])
                                left++;
                            while(left < right && nums[right] == nums[right - 1])
                                right--;
                            //更新left和right位置,寻找下一个四元组
                                left++;
                                right--;
                        }
                }
            }
        }
        return result;
    }
};

 五、总结

        三数之和和四数之和的问题,题目要求进行去重,再用哈希表来求解便很难,这里两道题用双指针法,进行剪枝去重操作。四数之和就是在三数之和的基础上,多套了一个for循环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值