930. 和相同的二元子数组

题目

给你一个二元数组 nums ,和一个整数 goal ,请你统计并返回有多少个和为 goal 的 非空 子数组。

子数组 是数组的一段连续部分。

示例 1:

输入:nums = [1,0,1,0,1], goal = 2
输出:4
解释:
有 4 个满足题目要求的子数组:[1,0,1]、[1,0,1,0]、[0,1,0,1]、[1,0,1]

示例 2:

输入:nums = [0,0,0,0,0], goal = 0
输出:15

提示:

  • 1 <= nums.length <= 3 * 104
  • nums[i] 不是 0 就是 1
  • 0 <= goal <= nums.length

思路

滑窗解法

将问题转换为 大于等于goal的数组个数 - 大于等于goal+1的数组个数 = 等于goal的数组个数

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        return numSubarraysLargerSum(nums, goal) - numSubarraysLargerSum(nums, goal + 1);
    }

    private int numSubarraysLargerSum(int[] nums, int goal) {
        int count = 0, left = 0, sum = 0;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= goal && left <= right) {
                sum -= nums[left];
                left++;
            }
            count += left;
        }
        return count;
    }
}

前缀和解法

什么是前缀和?

以示例一为例说明,现在有一个前缀和数组presum[]

  • presum[1]代表nums中前1位的和,也就是nums[0]
  • presum[2]是前两位的和,presum[2] = nums[0] + nums[1] = presum[1] + nums[1]

不难看出有以下规律:presum[n] = presum[n-1] + nums[n-1]

通过前缀和获取某个区间的和

比如现在求[2,4]区间的和:

presum[3]=presum[2]+nums[2]

presum[4]=presum[3]+nums[3]

presum[5]=presum[4]+nums[4]=presum[3]+num[3]+num[4]=presum[2]+num[2]+num[3]+num[4]

可得num[2]+num[3]+num[4]=presum[5]-presum[2]

那如何利用前缀和解题?

我们假设有这样一个区间(i,j]和刚好为goal。就有presum[j] - presum[i] = goal,这样我们枚举j,记录和刚好为 goal 的 i 的出现次数,每次查询符合条件的 i 的数量即可。

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        // 前缀和解法
        // 先记录前缀和,如果想知道(left, right]区间的和,可以通过presum[right] - presum[left]来得到。
        int[] presum = new int[nums.length + 1];
        for (int i = 1; i < nums.length + 1; i++) {
            presum[i] = presum[i - 1] + nums[i - 1];
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int result = 0;
        // presum[right] - presum[left] = goal,所以如果我们枚举右侧前缀和,只要减去goal,我们就可以知道需要值为多少的前缀和。
        // 再从hash表中找到以这个值为和的前缀有多少个,就能知道在当前右指针情况下有几个数组和为goal了
        for (int right = 0; right < nums.length; right++) {
            int r = presum[right + 1], l = r - goal;
            result += map.getOrDefault(l, 0);
            map.put(r, map.getOrDefault(r, 0) + 1);
        }
        return result;
    }
}
posted @ 2025-06-23 21:54  恒星同学  阅读(10)  评论(0)    收藏  举报