代码随想录算法训练营第七天_第三章_哈希表 | 454. 四数相加 II、383. 赎金信、15. 三数之和、18. 四数之和

LeetCode454. 四数相加 II

题目描述:给定四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,计算有多少个元组 (i, j, k, l) 能满足:0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

视频讲解https://www.bilibili.com/video/BV1Md4y1Q7Yh文章讲解https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E7%89%88%E6%9C%AC

  • 思路:
    • 从四个整数数组中分别取得四个值
    • 返回值为满足条件的元组个数,用map(key:值,value:出现次数)
    • 不能套太多层for(超时),考虑两两遍历,每次两层for
    • 先遍历nums1和nums2,map记录 {nums1[i] + nums2[j],出现次数}
    • 再遍历nums3和nums4,去map中查找匹配项,对于固定的nums3[k]和nums4[l],target出现了多少次,意味着构成了多少个有效的元组,所以应把value直接累加到result
  • 代码:
class Solution {
public:
    int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4) {
        // 存放nums1[i] + nums2[j] == key 即对应的{i, j}元组数(value)
        unordered_map map;
        int reslut = 0;
        // 先遍历nums1和nums2
        for (int a : nums1) {
            for (int b : nums2) {
                ++map[a + b];
            }
        }
        // 再遍历nums3和nums4
        for (int c : nums3) {
            for (int d : nums4) {
                int target = - (c + d);
                // 找到
                if (map.find(target) != map.end()) {
                    reslut += map.find(target)->second;
                }
            }
        }
        return reslut;
    }
};

LeetCode383. 赎金信

题目描述:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

文章讲解https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html#%E6%80%9D%E8%B7%AF

  • 思路:
    • 字母异位词:同样的字母构成,record分别++和--统计26个字母出现次数,最后看record是否全为0
    • 赎金信:ransomNote是magazine子集,record先++统计magazine,再把ransomNote用掉的--,最后看record是否全为正
  • 代码:
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        vector record(26, 0);
        // 统计magazine中各小写字母出现次数
        for (int i = 0; i < magazine.size(); i++) {
            ++record[magazine[i] - 'a'];
        }
        // 遍历ransomNote,用掉的字母相应减1
        for (int i = 0; i < ransomNote.size(); i++) {
            --record[ransomNote[i] - 'a'];
        }
        // 若record存在负数,则不能构成
        for (int i = 0; i < 26; i++) {
            if (record[i] < 0) return false;
        }
        return true;
    }
};

LeetCode15. 三数之和

题目描述:给定一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时 nums[i] + nums[j] + nums[k] == 0 。返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组

  • 对于全为0的数组,只需返回{0, 0, 0}这一个三元组

视频讲解https://www.bilibili.com/video/BV1GW4y127qo文章讲解https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html#%E5%93%88%E5%B8%8C%E8%A7%A3%E6%B3%95

  • 分析:
    • 两数之和:只会存在一个有效答案,需要返回下标(用map),找到直接return即可
    • 三数之和:存在多个可能的答案(不重复),需要返回元素值,找到一组记录一组
    • a + b + c == 0,a 不要取到相同的值,b、c 同理,但三者之间可以相等
    • 不妨先将 nums 排序,这样一方面可以通过取 a < b < c,避免因顺序不同而重复;另一方面值相等的元素将处于连续位置,便于后续去重操作
  • 思路:
    • 哈希法:两层 for 循环
      • 第一层 for 从 0 开始遍历,取得 nums[i] (作为a)
        • 如果此时 nums[i] > 0,就不必再找了
        • 如果 i 跟 前一个元素值相等了,跳过
        • 对每一个固定的 i,创建 set 记录 j 遍历过的元素值
        • 第二层 for 从 i + 1 开始遍历,取得nums[j](作为c)
          • 如果 j 跟 前两个元素值相等了,跳过
          • 到 set 中寻找与 nums[i]、nums[j] 相匹配的 target:
            • 找到,用 result 记录,并从 set 移除 target(防止下一轮 j 取相同值)
            • 找不到,将 nums[j] 放入 set(作为b的备选)
    • 双指针法:
      • 第一层 for 从 0 开始遍历,取得 nums[i](作为a)
        • 如果此时 nums[i] > 0,就不必再找了
        • 如果 i 跟 前一个元素值相等了,跳过
        • left 从  i + 1 向右(作为b),right 从最右向左(作为c)
        • 内层循环while
          • a + b + c < 0 则右移 left
          • a + b + c > 0 则左移 right
          • a + b + c == 0 用 result 记录,并进行如下去重操作:
            • 如果 left 跟后一个值相等,右移,直到 left 卡到连续相等值的最右处
            • 如果 right 跟前一个值相等,左移,直到 right 卡到连续相等值的最左处
          • 右移 left,左移 right(正好跳过连续相等值)
  • 代码:
// 哈希法
class Solution {
public:
    vector> threeSum(vector& nums) {
        vector> result;
        // 先排序
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > 0) break;
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            // 存放j遍历过的元素值
            unordered_set jset;
            for (int j = i + 1; j < nums.size(); j++) {
                if (j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]) continue;
                int target = - (nums[i] + nums[j]);
                auto iter = jset.find(target);
                // 找到
                if (iter != jset.end()) {
                    result.push_back({nums[i],target,nums[j]});
                    jset.erase(iter);
                } else {
                    jset.insert(nums[j]);
                }
            }
        }
        return result;
    }
};
// 双指针法
class Solution {
public:
    vector> threeSum(vector& nums) {
        vector> result;
        // 先排序
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > 0)  {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) {
               continue; 
            }
            int left = i + 1;
            int right = nums.size() - 1;
            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({nums[i], nums[left], nums[right]});
                    // 去重
                    while (left < right && nums[left] == nums[left + 1]) ++left;
                    while (left < right && nums[right] == nums[right - 1]) --right;
                    ++left;
                    --right;
                }
            }
        }
        return result;
    }
};

LeetCode18. 四数之和

题目描述:给定一个整数数组 nums 和一个目标值 target,找出并返回所有满足nums[a] + nums[b] + nums[c] + nums[d] == target 的不重复四元组 [nums[a], nums[b], nums[c], nums[d]] 。其中,a、b、c、d互不相等。

视频讲解https://www.bilibili.com/video/BV1DS4y147US/?vd_source=f98f2942b3c4cafea8907a325fc56a48文章讲解https://programmercarl.com/0018.%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.html

  • 思路:
    • 延续三数之和思想,内层还是用 left 和 right 双指针确定 nums[c] 和 nums[d]
    • 外面两层 for 循环确定 nums[a] 和 nums[b]
    • 注意区别:和为 target 而不是给定的 0 ,所以不再直接以 nums[i] > target 剪枝(可能取到负数)
    • 二级剪枝时以 nums[i] + nums[j] 为整体,与 target 比较
    • 如果直接写 nums[i] + nums[j] + nums[left] + nums[right] > target 会溢出
    代码:
class Solution {
public:
    vector> fourSum(vector& nums, int target) {
        vector> result;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            // 一级剪枝
            if (nums[i] > target && nums[i] >= 0) {
                break;
            }
            // 一级去重
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < nums.size(); j++) {
                // 二级剪枝
                if (nums[i] + nums[j] > target && nums[j] >= 0) {
                    break;
                }
                // 二级去重
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = nums.size() - 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({nums[i], nums[j], nums[left], nums[right]});
                        // 去重
                        while (left < right && nums[left] == nums[left + 1]) {
                            ++left;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            --right;
                        }
                        // 找到答案并记录后,双指针同时收缩
                        ++left;
                        --right;
                    }
                }
            }
        }
        return result;
    }
};

你可能感兴趣的:(哈希表,leetcode)