day7 | 四数相加、三数之和、四数之和

78 阅读2分钟

四数相加 II

题目链接:leetcode.cn/problems/4s…

要点

  • a+b转为map的形式存储其相加的所有信息 key:a+b ; value:次数
  • 再用c+d去判断是否存在四数之和为0
  • a+bmap的时候存在重复,体现在value表示次数上;c+d两层for循环也存在重复

map写法

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2,
                                    vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> AplusB;
        for (int a : nums1){
            for (int b : nums2){
                AplusB[a+b]++;
            }
        }

        int count = 0;
        for (int c : nums3){
            for (int d : nums4){
                if (AplusB.count(-(c+d))) {
                    count += AplusB[-(c+d)];     //含重复的数对,但按下标元组看是不重复
                }
            }
        }
        return count;
    }
};

总结

暴力法复杂度为n^4,分为2+2就是n^2 + n^2复杂度,所以是22分不是13或者31分。

383. 赎金信

题目链接:leetcode.cn/problems/ra…

要点

  • 用数组做哈希表,242.有效的字母异位词的进阶题

数组哈希写法

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int mag[30] = {0};
        //录入magazine
        for (int i = 0; i < magazine.size(); i++) {
            mag[magazine[i]-'a']++;
        }
        //检测ransomNote
        for (int i = 0; i < ransomNote.size(); i++) {
            if (mag[ransomNote[i]-'a'] > 0) {
                mag[ransomNote[i]-'a']--;
            }else {
                return false;
            }
        }
        return true;
    }
};

总结

magazine中的字符每个只能用一次,思路还是很清晰的

15.三数之和

题目链接:leetcode.cn/problems/3s…

要点

  • a+b+c=0a去重比较的是前一个元素,以免错过{-1, -1, 2}这样的三元组
  • b, c的去重放在写入result容器之后,防止下一次放入相同的三元组。
  • 去重放在--位置的话,意义不大,总是要做++, --操作的,只是多加了判断

双指针写法

// 主要是对三个元素都要去重,有很多细节
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++){
            int left = i+1;
            int right = nums.size()-1;

            if (nums[i] > 0) {
                break;
            }
            //第一个元素去重,和前一个元素比相等,而不是后一个(先找,别着急跳)
            if (i>0 && nums[i]==nums[i-1]) {
                continue;
            }

            while (left < right) {
                if (nums[i]+nums[left]+nums[right] < 0) left++;        //--
                else if (nums[i]+nums[left]+nums[right] > 0) right--;  //--
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});

                    // 另外两个元素的去重
                    while (left<right && nums[left] == nums[left+1]) left++;
                    while (left<right && nums[right] == nums[right-1]) right--;

                    //两边内收,继续找b c
                    left++;right--;
                }
            }
        }
        return result;
    }
};

总结

对三个元素去重的思考,都是先完成当下的操作再考虑下一个是不是重复的需要跳过

双指针将暴力法的n^3降为n^2,还用哈希法的话,不好做去重

15.四数之和

题目链接:leetcode.cn/problems/4s…

要点

  • 剪枝时,需要nums[k] > 0,避免{-4, -2, -1, 2, 5} target = -5,这种加负数越加越小的情况,将存在解的枝剪掉
  • bug1:nums.size()-3, nums.size()-2这种写法,没有之前的判断,鲁棒性差
  • bug2:int的范围是 - 2 ^ 31 ~2 ^ 31 - 1;也就是:[-2147483648, 2147483647] (10位),四项相加超过了int上限

双指针写法

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());

        if (nums.size() < 4) {            //bug1
            return result;
        }

        for (int k = 0; k < nums.size()-3; k++) {   //bug1,原解的鲁棒性差,导致越界此类内存问题
            //剪枝+去重
            if (nums[k]>target && nums[k]>=0) break;
            if (k>0 && nums[k]==nums[k-1]) continue;    //k去重

            for (int i = k+1; i < nums.size()-2; i++) {
                if (nums[k]+nums[i] > target && nums[k]+nums[i] >= 0) break;
                if (i>k+1 && nums[i]==nums[i-1]) continue;  //i去重

                int left = i+1;
                int right = nums.size()-1;
                while(left < right) {
                    if ((long) nums[k]+nums[i]+nums[left]+nums[right] > target) right--;      //bug2
                    else if ((long) nums[k]+nums[i]+nums[left]+nums[right] < target) left++;
                    else {
                        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--;
                    }
                }
            }

        }
        return result;
    }
};

总结

思路同三数之和,就是要考虑4个指针k, i, left, right的去重,以及前两个用for遍历的指针的剪枝细节。