力扣560. 和为 K 的子数组

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

示例 1:

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

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/subarray-sum-equals-k

        Leetcode中的题解为,以 i 结尾,和为 k 的连续子数组个数(太反人类了,看来半天没看懂官方的哈希表法)。这里重新记录一下,以 i 起始,和为 k 的连续子数组个数。更符合自己的解题思路。


一、暴力枚举

示例:[3, 4, 7, 2, -3, 1, 4, 2]   k=7

           [3, 4, 7, 2, -3, 1, 4, 2]

            i    j →→

               [4, 7, 2, -3, 1, 4, 2]

                i  , j →→

定义由 i 开始的子区间 [ i, j ], 逐步向右搜索,统计该区间内 所有数的和 是否等于 k:

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


二、暴力枚举 + 前缀和

        定义前缀和数组 preSum[],其中preSum[i]表示:nums[0, ..., i] 区间所有数的和。

        显然有:preSum[i] = preSum[i - 1] + nums[i]

        因此,若区间 [ i, j ],满足式 k == preSum[j] - preSum[i - 1],则满足题意。

即:

        题意:有几种 i、j 的组合,使得从第 i 到 j 项的子数组和等于 k。
        ↓ ↓ ↓ 转化为 ↓ ↓ ↓
        有几种 i、j 的组合,满足 preSum[j] - preSum[i - 1] == k

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int count = 0;
        
        // 前缀和数组
        vector<int> preSum(nums.size(), 0);
        preSum[0] = nums[0];
        for(int i=1;i<nums.size();++i)
        {
            preSum[i] = preSum[i-1] + nums[i];
        }

        // [1, 1, 1, 1, 1, 1]
        //    (i     j)
        for(int i=0; i<nums.size(); ++i){
            for(int j=i; j<nums.size(); ++j){
                int sum;
                if(i==0)
                    sum = preSum[j] - 0;    //即 preSum[i - 1] 定义为0
                else
                    sum = preSum[j] - preSum[i - 1];
                if(sum == k)
                    ++count;
            }
        }
        return count;
    }
};

三、前缀和 + 哈希表优化

我们可以基于方法一利用数据结构进行进一步的优化,我们知道方法一的瓶颈在于对每个 i,我们需要枚举所有的 j 来判断是否符合条件,这一步是否可以优化呢?答案是可以的。

  • 由于只关心次数,不关心具体的解,我们可以使用哈希表加速运算;
  • 由于保存了之前相同前缀和的个数,计算区间总数的时候不是一个一个地加,时间复杂度降到了 O(N)。

这个思路不是很容易想到,需要多做一些类似的问题慢慢培养感觉。

同类问题有:
「力扣」第 1 题:两数之和;
「力扣」第 1248 题: 统计「优美子数组」;
「力扣」第 454 题:四数相加 II。

        题意:有几种 i、j 的组合,使得从第 i 到 j 项的子数组和等于 k。
        ↓ ↓ ↓ 转化为 ↓ ↓ ↓
        有几种 i、j 的组合,满足 preSum[j] - preSum[i - 1] == k

                                            即 preSum[j] - k == preSum[i - 1]

        其实我们不关心具体(即可以考虑用HashMap)是哪两项的前缀和之差等于k,只关心等于 k 的前缀和之差出现的次数c,就知道了有c个子数组求和等于k。

                                        key -- 前缀和为key     value -- 前缀和为k出现的次数

  • 遍历 nums 之前,我们让 i=0 即 preSum[ i-1] = preSum[-1] 对应的前缀和为 0 (即定义:0之前的前缀和 = 0),这样通式在边界情况也成立。即在遍历之前,map 初始放入 0:1 键值对(前缀和为0出现1次了)。
  • 用 j 遍历 nums 数组,求每一项的前缀和 preSum[j],并统计对应的出现次数,以键值对存入 map。
  • 边存边查看 map,如果 map 中存在 key 为「当前前缀和 preSum[j] - k」,说明这个之前出现了某个前缀和preSum[i - 1] ,满足「当前前缀和 - 该前缀和 == k」,它出现的次数,累加给 count。
  • class Solution {
    public:
        int subarraySum(vector<int>& nums, int k) {
            int count = 0;
            unordered_map<int, int> preSumCountMap;
            // 前缀和数组
            // key :前缀和为key     value :前缀和为k出现的次数
            preSumCountMap[0] = 1;
            int preSum=0;
            for(int j = 0; j < nums.size(); ++j)
            {
                preSum += nums[j];
                if(preSumCountMap.count(preSum - k))
                    count += preSumCountMap[preSum - k];
                preSumCountMap[preSum]++;
            }
    
            return count;
        }
    };
    

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值