秒杀leetcode子数组问题的模板“前缀和+哈希表”

本文系转载

作者:大鱼
链接:https://leetcode.cn/problems/subarray-sum-equals-k/solutions/1485461/by-da-yu-bt-ge0y/
来源:力扣(LeetCode)

有大佬总结了用模板解决四个《前缀和+哈希表》问题,总结的不错,加上这类题几乎是面试必考题,在这里搬用一下。

leetcode 560.和为 K 的子数组:https://leetcode.cn/problems/subarray-sum-equals-k/
leetcode 1248. 统计「优美子数组」: https://leetcode.cn/problems/count-number-of-nice-subarrays/
leetcode 974.和可被 K 整除的子数组:https://leetcode.cn/problems/subarray-sums-divisible-by-k
leetcode 523. 连续的子数组和:https://leetcode.cn/problems/continuous-subarray-sum/
leetcode 1512. 好数对的数目 https://leetcode.cn/problems/number-of-good-pairs/

leetcode 560.和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。

子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2

示例 2:
输入:nums = [1,2,3], k = 3
输出:2

提示:

1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107

简洁版代码

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int res = 0, n = nums.size();
        for (int i = 0; i < n; ++i) {
            int sum = nums[i];
            if (sum == k) ++res;
            for (int j = i + 1; j < n; ++j) {
                sum += nums[j];
                if (sum == k) ++res;
            }
        }
        return res;
    }
};

前缀和+hash表

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int res = 0;
        int sum = 0;
        
        unordered_map<int,int>umap={{0,1}};
        for (int n : nums){
            sum += n;
            int cur = sum - k;
            if(umap.count(cur)) {
                res += umap[cur];
            }
            umap[sum]++;
        }
        return res;
    }
};

以上代码实现了一个函数 subarraySum,用于计算一个整数数组 nums 中所有和为 k 的子数组的数量。

函数首先初始化两个变量 res 和 sum,分别用于存储结果和当前子数组的和。然后,函数创建一个无序映射 umap,用于存储每个前缀和及其出现的次数。umap 的初始值为 {0, 1},表示前缀和为 0 的子数组出现了 1 次。

接着,函数遍历数组 nums 中的每个元素 n,将其加到 sum 上,然后计算当前子数组和减去 k 的结果 cur。如果 cur 在 umap 中存在,则表示存在一个前缀和为 cur 的子数组,其和为 k,因此将 umap[cur] 加到 res 上。最后,函数将当前子数组的和 sum 及其出现的次数加到 umap 中。
最后,函数返回 res,即所有和为 k 的子数组的数量。
问题解析
1.为什么使用unordered_map?
答: 相比于map, unordered_map底层使用哈希表实现,是真正意义上的“哈希表”。(map底层实现为红黑树)(unordered_map在查询和插入上都比map快,但是缺少自动排序功能)
2.mp存在的意义是什么?
答: 假设当前前缀和为sum,我们的目标是求解一个连续的和为k的子数组,假设子数组标号从i到j,即求nums[i] + ... +nums[j] = k,转化为前缀和就是sum[j] - sum[i-1] = k。使用哈希时,我们在从左往右遍历nums,很明显,preSum为遍历到当前num的前缀和,即sum[j]。那sum[i-1]去哪里了?所以我们需要使用一个数据结构,以便在我们需要之前的preSum时能够快速找到。
3.mp是如何使用的?
答: 由于题目要求我们求解sum[j] - sum[i-1] = k的区间个数,我们将sum[i-1]的有关信息放入mp,而当前状态我们只有sum,即sum[j]。对问题进行变形,可以转换为满足求解sum[i-1] == sum[j] - k的区间个数,即如果之前就存在前缀和为sum[i-1]的区间n个,那么满足sum - k的区间也会是n个。所以我们实际在mp中存放的内容就是前缀和为sum[i-1]的区间的个数。
为什么初始化mp[0] = 1?
答: 刚开始还没有遍历nums,可以得到前缀和为0的区间有1个(区间内不包含任何元素)。
为什么要给mp[sum]++?
答: 因为我们mp中记录的是前缀和为sum的区间个数!!!比如数据[1, 2, 3, -3, 3]中,前缀和sum为3的有[1, 2]和[1, 2, 3, -3]两个,因此如果求解k=6的子数组,遍历到最后一个num时,根据这两个区间可以得到答案[3, -3, 3]和[3](最后一个3)(当然答案还有[1, 2, 3],这个在前面就已经处理了)。

leetcode 1248. 统计「优美子数组]
给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。

请返回这个数组中 「优美子数组」 的数目。

示例 1:

输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:

输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。
示例 3:

输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
AC代码

class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k) {
        unordered_map<int,int>umap={{0,1}};
        int sum=0,ans=0;
        for(auto& a:nums){
            sum+=(a&1);
            int cur=sum-k;
            if(umap.count(cur)){
                ans+=umap[cur];
            }
            umap[sum]++;

        }
    return ans;
    }
};

974.可被 K 整除的子数组

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的(连续、非空) 子数组 的数目。子数组 是数组的 连续 部分。
示例 1:
输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]
示例 2:

输入: nums = [5], k = 9
输出: 0

提示:

1 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
2 <= k <= 104

974题答案:

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int,int>umap={{0,1}};
        int ans=0,sum=0;

        for(auto& a:nums){
            sum+=a;
            int cur=(sum%k+k)%k;
            if(umap.count(cur)){
                ans+=umap[cur];
            }
            
            umap[cur]++;

        }
    return ans;
    }
};

leetcode 523.连续的子数组和
给你一个整数数组 nums 和一个整数 k。编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:
子数组大小 至少为 2 ,且子数组元素总和为 k 的倍数。
如果存在返回 true ,否则返回 false

如果存在一个整数 n ,令整数 x 符合x = n * k ,则称 x 是 k 的一个倍数。0 始终视为 k 的一个倍数。

示例 1:
输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。
示例 2:

输入:nums = [23,2,6,4,7], k = 6
输出:true
解释:[23, 2, 6, 4, 7] 是大小为 5 的子数组,并且和为 42 。
42 是 6 的倍数,因为 42 = 7 * 6 且 7 是一个整数。
示例 3:

输入:nums = [23,2,6,4,7], k = 13
输出:false

提示:

1 <= nums.length <= 105
0 <= nums[i] <= 109
0 <= sum(nums[i]) <= 231 - 1
1 <= k <= 231 - 1

523题答案:

class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        unordered_map<int,int>umap={{0,-1}};
        int ans=0,sum=0;

        for(int i=0;i<nums.size();i++){
            sum+=nums[i];
            int cur=sum%k;
            
            if(umap.count(cur)){
                if(i-umap[cur]>=2)return true;
                continue;
            }
        umap[cur]=i;


        }
    return false;
    }
}

1512. 好数对的数目

给你一个整数数组 nums 。
如果一组数字 (i,j) 满足 nums[i] == nums[j] 且 i < j ,就可以认为这是一组好数对 。
返回好数对的数目。

示例 1:

输入:nums = [1,2,3,1,1,3]
输出:4
解释:有 4 组好数对,分别是 (0,3), (0,4), (3,4), (2,5) ,下标从 0 开始
示例 2:

输入:nums = [1,1,1,1]
输出:6
解释:数组中的每组数字都是好数对
示例 3:

输入:nums = [1,2,3]
输出:0

提示:

1 <= nums.length <= 100
1 <= nums[i] <= 100

1512题目答案

class Solution {
public:

    int numIdenticalPairs(vector<int>& nums) {
        std::unordered_map<int, int> countMap;
        int pairsCount = 0;
        for (int i = 0; i < nums.size(); ++i) {
            // 检查当前元素在哈希表中的计数,如果有则表示存在相同的值
            if (countMap.count(nums[i])) {
                pairsCount += countMap[nums[i]];
            }
            // 更新当前元素在哈希表中的计数
            countMap[nums[i]]++;
        }
        return pairsCount;
    }
};
  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值