力扣 | 哈希表2 | 454.四数相加||,383.赎金信,15.三数之和,18.四数之和

目录

454.四数相加||

383.赎金信

15.三数之和

18.四数之和

总结


454.四数相加||

解决办法:

        总体思路是先遍历前两个数组得到a+b以及相加后的结果的次数,再遍历后两个数组得到c+d,然后在前面两个数组所得到的集合中去找是否有0-(c+d)的值,

  • 为何用哈希表:这里需要查找是否出现过
  • 为何用map:哈希值由题可能会很大,不仅要找到该元素,还要找到其所出现的次数。故key来保存数值,value保存次数。即key,value并不总是一个保存元素一个保存下标的关系,具体对应什么,根据题目而定。
  • 本题不需要去重,四个数组,可以有重复的四个元素相加等于一的情况,即需要统计次数。不同位置的元组,次数都会加一。

        语法:map[a+b]++可以实现对应元素的value相应+1操作,以及insert(a+b)

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> map;
        // 前两个数组
        for (int a : nums1) {
            for (int b : nums2) {
                map[a + b]++; // 统计数值和出现次数
            }
        }
        // 后两个数组
        int count = 0;
        for (int c : nums3) {
            for (int d : nums4) {
                int target = 0 - (c + d); // 需找的元素
                if (map.find(target) != map.end()) {
                    count += map[target]; // 加value而不是单纯+1
                }
            }
        }
        return count;
    }
};

时间复杂度O(n^2)

空间复杂度O(n^2)

383.赎金信

解决办法:和有效的字母异位词相似。题目说了也是只有小写字母,故可以用长度26的数组。注意长度有效性的判断。

该题也可以暴力法两个for循环(ransomNote.erase(ransnomNote.begin()+j,在ransomNote找到该字符了就删除,最终ransomNote.length()==0为空,则true),只不过不用额外开辟数组空间,但时间低下了。

length()用于字符串的字符数量,size()用于容器vector的大小。字符串能用索引[]

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int nums[26] = {0};
        // 后者长度不够,直接pass
        if (magazine.length() < ransomNote.length()) {
            return false;
        }
        for (int i = 0; i < magazine.length(); i++) {
            nums[magazine[i] - 'a']++; // 记录后者各个字符出现的次数
        }
        for (int i = 0; i < ransomNote.length(); i++) {
            // 只有该字符已经在magzine中出现过,才可以减
            if (nums[ransomNote[i] - 'a'] > 0) {
                nums[ransomNote[i] - 'a']--;
            } else {
                return false;
            }
            
        }
        return true;
    }
};

时间复杂度O(n)

空间复杂度O(1)

15.三数之和

解决办法:本题注意,需要去重,三元组内的元素不可和另一个三元组重复即使元素来自不同下标。

        双指针法(要排序),将时间复杂度O(n^2)的解法优化为O(n)的解法,即降一个数量级

        将数组由低到高进行排序后,for循环,i从数组第一个位置开始,left从第二个位置开始,right从最后一个位置开始,三个数相加大于零,则right--,三数相加小于零,则left++,等于零,则找到一组。这个过程里,i,left,right都要进行去重,比如nums[i]==nums[i-1]则说明这个元素及其所对应的那一组已经出现过了,就要进行i++的操作,继续去找下一组才行。(注意这里不是判断nums[i]==nums[i+1],这种是判断的三元组内的三个元素是否重复了,这种由题意是可以的)。有点像滑动窗口,left和right间的区域也是在不断缩小以找到所需元素。

        注意去重的写法以及去重和获取结果间的先后关系。left和right的去重,注意是使用while而不是if,因为可能连着几个数都是一样的。找到结果后,双指针得同时收缩。i是与前一个比,前面有了,就直接下一个;left,right和下一个比,已经收集了,就不收集了。i定了,后面的left和right也是定了。

        sort(nums.begin(), nums.end());

        vector<vector<int>> result;

        result.push_back(vector<int>{nums[i], nums[left], nums[right]});

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(); i++) {
            // 数组最小元素都大于零了,直接无符合三元组(剪枝)
            if (nums[i] > 0) { 
                return result;
            }
            // 前提i>0,才可i-1。i去重
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            int left = i + 1;
            int right = nums.size() - 1;
            // right==left时,后面两个元素都合二为一变成同一个元素了,哪来三元组
            while (right > 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]});
                    // 先获取到结果了再对left,right去重。
                    // 比如数组是[0,0,0,0,0]这种情况,如果先去重,则就一直都获取不到结果
                    // 用while,可能连着几个数都重复
                    // 前提仍需right > left,外层的right>left是外层的,这里在去重时,可能就不满足这个前提了,所以需要加入If条件里
                    // 注意这里是left和left+1,right和right-1!!!!!!已经收割到一组了,故接下来重复的直接跳过,不同于i
                    while (right > left && nums[left] == nums[left + 1]) { 
                        left++;
                    }
                    while (right > left && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    // 找到时,双指针需同时收缩
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
};

        时间复杂度O(n^2)for和while

        空间复杂度O(1)题目本身给的就是二维空间了

18.四数之和

解决办法:

        类似三数之和,也用双指针,不过这里有两个for循环,两重剪枝和去重。

        注意两个负数相加可能更小。这里是target不是三数之和那样的0,该target可能为负数,故不能简单剪枝nums[k]>target,还需要前提nums[k]>=0 || target>=0.

        三数之和的解法是一层for循环nums[i]为确定值,循环内有left和right双指针;四数之和的解法是两层for循环nums[k]+nums[i]为确定值,依然循环内是left和right双指针。

        四数相加可能极大,为避免溢出,故需转为long类型

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++) {
            // 一重剪枝
            if (nums[k] > target && target >= 0) break; // 使用break,统一通过最后的return返回
            // 一重去重
            if (k > 0 && nums[k] == nums[k - 1]) continue;
            for (int i = k + 1; i < nums.size(); i++) {
                // 二重剪枝(k和i一起)
                if (nums[k] + nums[i] > target && nums[i] + nums[k] >= 0) break;
                // 二重去重
                if (i > k + 1 && nums[i] == nums[i - 1]) continue;
                // 类似三数之和的逻辑,只不过这里变成了四元
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    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 {
                        result.push_back({nums[k], nums[i], nums[left], nums[right]});
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;
                        // 双指针收缩
                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }
};

        时间复杂度O(n ^ 3)

        空间复杂度O(1)

总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值