算法训练营DAY7 第三章 哈希表part02

第454题.四数相加II

454. 四数相加 II - 力扣(LeetCode)

解题思路:使用unordered_map,用两个for循环遍历A与B数组所有的可能的a+b组合,以及每种组合出现的次数;接下来在两个for循环中遍历所有的target=0-(c+d),在map中查询每个target是否出现,若出现则count+=map[target](map的键值,相当于该种target的出现次数);最终输出count。

class Solution {
public:
    int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4) {
        std::unordered_map map;
        int count=0;
        for(int a:nums1){
            for (int b:nums2){
                map[a+b]++;
            }
        }
        for(int c:nums3){
            for (int d:nums4){
                int target=0-(c+d);
                if(map.find(target)!=map.end()){
                    count+=map[target];
                }
            }
        }
        return count; 
    }
};

383. 赎金信

383. 赎金信 - 力扣(LeetCode)

解题思路:遇到字母,遇到内容是否出现在一个集合,哈希表应该要第一时间想到,继续思考需要何种数据类型,发现仅存放26种字母,因此选取数组;首先用for循环统计magazine中所有字母出现的频数,再用for循环遍历ransomNote的每一个字母,减去每个字母出现的频数,在循环中比较频数是否<0,若小于0则意味着magazine中的频数不足以cover  ransomNote中需要的该字母频数。反之则可以cover。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int alpha[26]={0};
        if(ransomNote.size()>magazine.size()){return false;}
        for(int i=0;i

第15题. 三数之和

15. 三数之和 - 力扣(LeetCode)

解题思路:对原始数据进行排列,简化去重操作。升序排列后,如果a>0则可以直接输出空数组。

1、哈希表外层for循环的表示a,内层for循环表示c,引入target=0-(a+c);target会放入set哈希表中;每次循环中查找target是否出现在set中,如果存在则输出一组三元组,不存在就继续循环。其中去重的思路很重要,首先是a的去重,注意是nums[i-1]==nums[i],a与上一个a作比较;b、c的去重则需要连着比较三个数,且是与遍历过的数比较;目的是避免将本来需要输出的结果跳过。

2、双指针,外层一个for循环i代表a,引入指针left与right,left=i+1,right=nums.size()-1;左指针从i的下一个开始,右指针从末尾开始;如果三个数的和小于0,意味着需要更大的数即left向右移动;如果和大于0,意味着需要更小的数即right左移;当遇到和为0时,输出三元组。同样也需要去重,这里的去重思路稍微简单一些,a、b、c都只需要与上一个abc作比较。

哈希表

class Solution {
public:
    vector> threeSum(vector& nums) {
        vector> res;
        sort(nums.begin(),nums.end());

        for(int i=0;i0){break;}
            if(i>0&&nums[i]==nums[i-1]){continue;}
            unordered_set set;
            for(int k=i+1;ki+2&&nums[k]==nums[k-1]&&nums[k-1]==nums[k-2]){continue;}
                int target=0-(nums[i]+nums[k]);
                if(set.find(target)!=set.end()){
                    res.push_back({nums[i],target,nums[k]});
                    set.erase(target);
                }
            else{set.insert(nums[k]);}
            }
        }
        return res;
    }
};

双指针法

class Solution {
public:
    vector> threeSum(vector& nums) {
        vector> res;
        sort(nums.begin(),nums.end());

        for(int i=0;i0){break;}
            if(i>0&&nums[i]==nums[i-1]){continue;}
            int left=i+1;
            int right=nums.size()-1;
            while(right>left){
                if(nums[i]+nums[left]+nums[right]>0)right--;
                else if(nums[i]+nums[left]+nums[right]<0)left++;
                else{
                    res.push_back(vector{nums[i],nums[left],nums[right]});
                    while(right>left&&nums[right]==nums[right-1])right--;
                    while(right>left&&nums[left]==nums[left+1])left++;
                    right--;
                    left++;
                }
            }
        }
        return res;
    }
};

第18题. 四数之和

18. 四数之和 - 力扣(LeetCode)

解题思路:本题与上一道题三数之和为零的思路类似,只需要加一层循环,但需要注意判断nums[k] > target && (nums[k] >=0 || target >= 0)需要加上nums[k]或者target大于零的条件。若都为负数则不能剪枝,k=-4 target=-10,仍有可能找到满足要求的四个数字。

class Solution {
public:
    vector> fourSum(vector& nums, int target) {
        vector> result;
        sort(nums.begin(), nums.end());
        for (int k = 0; k < nums.size(); k++) {
            // 剪枝处理
            if (nums[k] > target && nums[k] >= 0) {
            	break; // 这里使用break,统一通过最后的return返回
            }
            // 对nums[k]去重
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for (int i = k + 1; i < nums.size(); i++) {
                // 2级剪枝处理
                if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
                    break;
                }

                // 对nums[i]去重
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    // nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
                    if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
                        right--;
                    // nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出
                    } else if ((long) nums[k] + nums[i] + nums[left] + nums[right]  < target) {
                        left++;
                    } else {
                        result.push_back(vector{nums[k], nums[i], nums[left], nums[right]});
                        // 对nums[left]和nums[right]去重
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;

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

            }
        }
        return result;
    }
};

感悟:哈希表的数据结构有三种数组、set、map,而set与map又有三种不同的实现,需要知道每种数据结构的特点以及背后的实现逻辑,这里还需要加强。再就是双指针法和哈希表方法的应用差别,需要确定存在元素的位置一般需要哈希表,不需要确定元素位置的可以尝试双指针。

你可能感兴趣的:(算法,哈希算法)