【代码随想录】Day7 哈希表part02 454.四数相加II 83. 赎金信 15. 三数之和 18. 四数之和

系列文章目录



前言

哈希表第二部分


454.四数相加II

Source: 题目
Note:
题目给定四个数组,要求满足
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

这里要用巧妙的方法:

  1. 我们将nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
    分解为 nums1[i] + nums2[j] = 数字1
    如果nums3[k] + nums4[l]是数字1的相反数,那么我们就满足了条件

  2. 所以如果把nums1和nums2的和作为key,和出现的次数作为value

  3. 这样遍历nums3+num4加和结果的时候在unordered_map中寻找如果出现加和结果的相反数,那么就说明有value个组合满足题意,count+=value
    如此循环找到所有的目标组合!

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> sum1;

        for (int i : nums1) {
           for (int j :nums2) {
               sum1[i + j]++;
           } 
        }

        int count = 0;

        for (int k : nums3) {
           for (int l :nums4) {
               if (sum1.find(-(k + l)) != sum1.end()){
                   count = count + sum1[-(k + l)];
               }
           } 
        }
        return count;
    }
};

Tips:

383. 赎金信

Source: 题目
Note:题目给定两字符串ransomNote magazine

  1. 如果ransomNote可以被magazine内的字符表示(每个字符只能用一次)则返回true 反之为false
  2. 容易想到使用字典法,int record[26];来表示26个小写英文字母
    使用[c - ‘a’]的相对值来获取数组的下标
  3. 我们很容易想到遍历ransomNote统计每个用到的字母数量,然后再遍历magazine给对应字符-1, 最后检查record如果有>0的字母个数,说明magazine没有满足ransomNote的需求,返回false
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26];
        
        for (char c : ransomNote) {
            record[c - 'a']++;
        }

        for (char c : magazine) {
            record[c - 'a']--;
        }

        for (int num : record) {
            if (num > 0) {
                return false;
            }
        }

        return true;
    }
};

Tips:

15. 三数之和

Source: 题目
Note:题目给定一个数组,返回所有的nums[i] + nums[j] + nums[k] == 0.
这道题使用双指针法减少运算量非常典型
这道题非常值得仔细琢磨,特别是对于下标的处理的部分

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        int len = nums.size();
		// 这里先排序非常重要!!!
        sort(nums.begin(), nums.end());
        if (len < 3 || nums[len-1] < 0 || nums[0] > 0) {
            return res;
        }

        for (int i = 0 ; i < nums.size() - 2; i++) {
        	// 注意下面这个剪枝操作
            if (i > 0 && nums[i] == nums[i-1]) continue;
            int left = i + 1;
            int right = nums.size() - 1;
            // i不变,移动left right
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];    
                if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;     
                } else if(nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                }else{
                    res.push_back(vector<int>{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--;
                    }
                }

            }
        }
        return res;
    }
};

Tips:

18. 四数之和

Source: 题目
Note:题目基本同上一道题,但是是4个数字的和 = target而不是0
相比上一道题,这道题多了更多细节的控制:
内外循环的双重剪枝
剪枝操作的不同(注意三数之和为0 与 四数之和为target 的区别)
四数加和会溢出,应使用long强制转型

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int len = nums.size();
        vector<vector<int>> ans;
        sort(nums.begin(),  nums.end());
        // 这里的检查和后面的双层循环范围互相制约,以防下标越界
        if (len < 4) {
            return ans;
        }   
        for (int i = 0 ; i < nums.size() - 3; i++) {
            // 注意这里的*剪枝处理* 当遍历到某位正数大于target时候,说明此后永远sum>target不存在目标组合
            if (nums[i] > target && nums[i] >= 0) {
            	break; // 这里使用break,统一通过最后的return返回
            }

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

            for (int j = i + 1; j < nums.size() - 2 ; j++) {
                // 这里注意j的初始范围和循环保持一致 j > i + 1
                if( j > i + 1&& nums[j-1] == nums[j]) continue;

                int left = j + 1;
                int right = nums.size() - 1;
                // 要小心数组越界 这个while循环 既能在i j确定情况下移动l r寻找答案
                // 又能避免数组下标越界,一旦越界,就进入下一次for j内循环
                while (left < right){
                    // 注意注释的操作会导致溢出 原因是赋值之前就已经溢出
                    // long int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    // 这里使用sum 而不是条件里面判断计算 可以减少计算量, 
                    long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];

                    if ( sum > target && left < right) {
                        right--;
                    }else if (sum < target && left < right){
                        left++;
                    }else{
                        ans.push_back({nums[i], nums[j], nums[left], nums[right]});
                        left++;
                        right--;
                        while (left < right && nums[left - 1] == nums[left]) left++;
                        while (left < right && nums[right] == nums[right + 1]) right--;
                    }

                }
               
            }
        }
        return ans;
    }
};

Tips:

总结

今天主要详细理解了unordered_map的用法, 三数之和, 四数之和。加深理解双指针法的应用!要严格控制数组下标谨防越界!
DAY 7 Finished 撒花~

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值