代码随想录算法训练营Day7 哈希?双指针!

哈希表和双指针相关题目

Leetcode 454. 四数相加 II

题目链接:https://leetcode.cn/problems/4sum-ii/description/

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

\[nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0 \]

其中$$0 <= i, j, k, l < n$$

题目解析:仿照”两数之和“的做法。原式等价于$$nums1[i] + nums2[j] = -nums3[k] -nums4[l]$$

那么,我们就可以用\(O(n^2)\)的时间获取所有的\(nums1[i] + nums2[j]\),然后用\(O(1)\)的从哈希表中查询\(-nums3[k] -nums4[l]\)

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        std::unordered_map<int, int> cnt;
        for (auto& x : nums1) {
            for (auto& y : nums2) {
                cnt[x + y] ++;
            }
        }

        int ans = 0;
        for (auto& x : nums3) {
            for (auto& y : nums4) {
                ans += cnt[-x - y];
            }
        }
        return ans;
    }
};

Leetcode 383. 赎金信

题目链接:https://leetcode.cn/problems/ransom-note/description/

题目描述:给你两个字符串:\(ransomNote\)\(magazine\) ,判断 \(ransomNote\) 能不能由 \(magazine\) 里面的字符构成。如果可以,返回 \(true\) ;否则返回 \(false\)\(magazine\) 中的每个字符只能在 \(ransomNote\) 中使用一次。

题目解析:这个题目可以理解成“商店购物”。本题的\(ransomNote\)就是顾客,\(magazine\)就是超市。我们先进货,也就是统计\(magazine\)的字符出现次数;然后遍历所有\(ransomNote\)所需要的字符,我们无条件地卖给他。如果发现卖了之后会出现负数,说明“供不应求”,就不能够满足题意。反之,满足了所有的顾客,说明符合题意。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
      // 剪枝 如果顾客太多 一定供不应求
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        int cnt[26]{};
        for (auto c : magazine) {
            cnt[c - 'a'] ++;
        }
        for (auto c : ransomNote) {
            if (-- cnt[c - 'a'] < 0) {
                return false;
            }
        }
        return true;
    }
};

Leetcode 15. 三数之和

题目链接:https://leetcode.cn/problems/3sum/description/

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

题目解析:根据“两数之和”的思路,我们枚举所有元素,当我们枚举元素\(i\)的时候,相当于固定下来了一个元素,那么我们就可以转化成“两数之和”。

// (法一:哈希)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        std::set<std::vector<int>> st;
        int n = nums.size();
        std::ranges::sort(nums);
        for (int i = 0; i < n; i ++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            unordered_set<int> seen;
            for (int j = i + 1; j < n; j ++) {
                int target = -nums[i] - nums[j];
                if (seen.count(target)) {
                    std::vector<int> tmp = {nums[i], nums[j], target};
                    std::ranges::sort(tmp);
                    st.insert(tmp);
                }
                seen.insert(nums[j]);
            }
        }
        return std::vector<std::vector<int>>(st.begin(), st.end());
    }
};

我们会发现,这样的时间复杂度是\(O(n^2)\)的,这显然在数据量比较大的时候是不优的。

那么我们有什么办法优化呢?双指针。

为了方便双指针以及去重,考虑排序。枚举\(nums[i]\),那么问题转化成另类的“两数之和”。为了避免重复,我们可以跳过相同的那些元素。

上代码,代码里面再解释。

// (法二:双指针)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        size_t n = nums.size();
        ranges::sort(nums);
        vector<vector<int>> ans;
        // 枚举所有的元素
        for (int i = 0; i <= n - 3; i ++) {
            int x = nums[i];
            // 三种剪枝优化

            // 去掉重复元素
            if (i && x == nums[i - 1]) continue;
            // 如果加上最小的还大 必不可能是答案
            if (x + nums[i + 1] + nums[i + 2] > 0) break; 
            // 如果加上最大的还小 必不可能是答案
            if (x + nums[n - 1] + nums[n - 2] < 0) continue;
            int l = i + 1, r = n - 1; // 从区间[i + 1, n - 1]找
            while (l < r) {
                int s = x + nums[l] + nums[r]; // 避免反复求和
                // 这里的思路是,因为整个数组有序
                // 那么 s > 0 -> 和大了 -> 让他变小 -> r左移
                // 同理 s < 0 -> 和小了 -> 让他变大 -> l右移 
                if (s > 0) {
                    r --;
                } else if (s < 0) {
                    l ++;
                } else {
                    // 因为已经有序,所以这样天然去重
                    ans.push_back({x, nums[l], nums[r]});
                    // 跳过相同的元素 避免去重
                    for (l ++; l < r && nums[l] == nums[l - 1]; l ++);
                    for (r --; l < r && nums[r] == nums[r + 1]; r --);
                }
            } 
        }
        return ans;
    }
};

这样,时间复杂度就变成了排序的\(O(n*logn)\),这显然是更优的。

Leetcode 18. 四数之和

题目链接:https://leetcode.cn/problems/4sum/description/

题目描述:给定一个由 \(n\) 个整数组成的数组 \(nums\),和一个目标值 \(target\)。请找出并返回满足下述全部条件且不重复的四元组 \([nums[a], nums[b], nums[c], nums[d]]\)(若两个四元组元素一一对应,则认为两个四元组重复)。
\(Note:\)

  1. \(0 \leq a, b, c, d < n\)
  2. \(a\)\(b\)\(c\)\(d\) 互不相同
  3. \(nums[a] + nums[b] + nums[c] + nums[d] = target\)

题目分析:有了上一题的经验,我们不难想到:枚举所有元素,就相当于固定下来了一个元素,那么这个问题就变成了一个三数之和。我们只需要再做一次三数之和就行了。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        using i64 = int64_t;
        ranges::sort(nums);
        int n = nums.size();
        vector<vector<int>> ans;
        for (int a = 0; a < n - 3; a ++) {
            i64 x = nums[a];
            if (a > 0 && x == nums[a - 1]) continue;
            for (int b = a + 1; b < n - 2; b ++) {
                i64 y = nums[b];
                if (b > a + 1 && y == nums[b - 1]) continue;
                int c = b + 1, d = n - 1;
                while (c < d) {
                    i64 s = x + y + nums[c] + nums[d];
                    if (s > target) {
                        d --;
                    } else if (s < target) {
                        c ++;
                    } else {
                        ans.push_back({(int) x, (int) y, nums[c], nums[d]});
                        for (c ++; c < d && nums[c] == nums[c - 1]; c ++);
                        for (d --; c < d && nums[d] == nums[d + 1]; d --);
                    }
                }
            }
        }
        return ans;
    }
};
posted @ 2025-08-28 19:34  Tangzy0121  阅读(506)  评论(0)    收藏  举报