代码随想录训练营第七天| 454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ● 18. 四数之和 ● 总结

 454.四数相加II

题目链 接/文章讲解/视频讲解: 代码随想录
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> map;
        for (int a: nums1) {        //将全部nums1和nums2中的元素和放到map的key中,并在map的value中记录重复的次数
            for (int b: nums2) {
                map[a+b]++;         //因为使用[]访问map会自动创建key值,并且value值会使用默认值来赋值
            }
        }
        int count = 0;
        for (int c: nums3) {
            for (int d: nums4) {
                if (map.find(0-c-d) != map.end()) {     //如果map中存在符合条件的元素和,则将它的value值加到count上
                    count += map[0-c-d];
                }
            }
        }
        return count;
}

这道题采用map来进行哈希表的建立。由于这道题目要求四个数组中相加为零的个数,首先要先计算前两个数组的元素和,将元素和放到map中的key中,用value记录等于该元素和的个数,然后再遍历后两个数组,寻找有没有符合条件的元素和,若存在,则直接将map中的value加到count上即可记录符合条件的次数。这道题实际上是使用哈希表,将一个四次嵌套循环分成了两个两次的嵌套循环,实现了使用空间换时间的效果,降低了时间复杂度。

383. 赎金信 

题目链接/文章讲解: 代码随想录
bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> map;
        for (int i = 0; i < magazine.size(); i++) {
            map[magazine[i]]++;
        }
        for (int i = 0; i < ransomNote.size(); i++) {
            if (map.find(ransomNote[i]) != map.end()) {
                map[ransomNote[i]]--;
                if (map[ransomNote[i]] == 0)
                    map.erase(ransomNote[i]);
            }
            else 
                return false;
        }
        return true;
    }

思路:先将第二个字符串的每个字母存在map的key中,用value来表示出现次数,再遍历第一个数组,如果遍历时字母在map中存在,则将其value值减一,若减少为0则说明第二个字符串中的这个字母用完了,将其从map中删掉。如果有不在map中的字母则不满足要求,返回false,否则在最后返回true。

 bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};
        //add
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        for (int i = 0; i < magazine.length(); i++) {
            // 通过record数据记录 magazine里各个字符出现次数
            record[magazine[i]-'a'] ++;
        }
        for (int j = 0; j < ransomNote.length(); j++) {
            // 遍历ransomNote,在record里对应的字符个数做--操作
            record[ransomNote[j]-'a']--;
            // 如果小于零说明ransomNote里出现的字符,magazine没有
            if(record[ransomNote[j]-'a'] < 0) {
                return false;
            }
        }
        return true;
    }

在给出的示例代码中是使用的数组实现的哈希表,由于只会有小写字母组成,使用数组实现时间消耗实际会小一些,但思路是一致的,使用map空间消耗可能会小一些。

15. 三数之和 

题目链接/文章讲解/视频讲解: 代码随想录
vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[j], c = -(a + b)
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
                continue;
            }
            unordered_set<int> set;
            for (int j = i + 1; j < nums.size(); j++) {
                if (j > i + 2
                        && nums[j] == nums[j-1]
                        && nums[j-1] == nums[j-2]) { // 三元组元素b去重
                    continue;
                }
                int c = 0 - (nums[i] + nums[j]);
                if (set.find(c) != set.end()) {
                    result.push_back({nums[i], nums[j], c});
                    set.erase(c);// 三元组元素c去重
                } else {
                    set.insert(nums[j]);
                }
            }
        }
        return result;
    }

首先是使用set的哈希表做法,这道题使用哈希表做法的话去重会比较复杂,不建议使用该方法。开始先进行排序,并将三元组元素a进行去重,若又连续两个相同的元素a,若第一个又满足条件的,第二个也一定满足,因此要将第二个跳过。i、j、c的实际顺序其实是i<c<j,因此,在set.erase(c);这个去重实际是去重第二个元素。j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]这个条件是用来去重b元素的,c是遍历j走过的数组值 如果有2个连续的就是C = j ,3个连续的j就重复了。实际上,在a确定之后符合条件的c+b的和就已将确定了,如果有三个相同的元素的话,在前两个元素遍历完之后,如果有满足条件的就已经被添加至结果数组里了,所以应该被去重。

vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
                return result;
            }
            // 错误去重a方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重a方法
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                /*
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                */
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if (nums[i] + nums[left] + nums[right] < 0) left++;
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }

        }
        return result;
    }

采用双指针做法会更加的高效,定义指针left,reigt,初始时,left=i+1,right=size-1。

如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

关于去重的内容,可以直接去看代码随想录中的内容,写的相当详细且易懂了。

18. 四数之和

题目链接/文章讲解/视频讲解: 代码随想录​​​​
vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        int length = nums.size();
        if (length < 4) {
            return result;
        }
        sort(nums.begin(), nums.end());
        for (int i = 0; i < length - 3; i++) {
            if (i > 0 && nums[i] == nums[i-1])
                continue;
            if ((long)nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target)
                break;
            for (int j = i+1; j < length - 2; j++) {
                if (j>i+1 && nums[j] == nums[j-1])
                    continue;
                int left = j+1, right = length-1;
                while (left < right) {
                    if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target)
                    left++;
                else if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target)
                    right--;
                else {
                    result.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
                    //left和right的去重操作
                    while (right > left && nums[left] == nums[left + 1])  left++;
                    while (right > left && nums[right] == nums[right - 1])  right--;
                    left++;
                    right--;
                }
                }
            }
        }
        return result;
    }

这道题和上一题思路上一致,都是使用双指针来解决,不过这道题要多加一套循环。在去重和剪枝上也都与上一题类似,不过这道题剪枝可以直接使用四个元素的大小来进行判断,更加精确。

但是我写的时候在left和right去重时right > left && 这个条件忘写了,导致一直造成数组越界,还找了半天bug,真是不注意细节啊!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值