930. Binary Subarrays with Sum - Explanation

Problem Link

Description

You are given a binary array nums and an integer goal, return the number of non-empty subarrays with a sum goal.

A subarray is a contiguous part of the array.

Example 1:

Input: nums = [1,0,1,0,1], goal = 2

Output: 4

Explanation: The 4 subarrays are bolded and underlined below:
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]

Example 2:

Input: nums = [0,0,0,0,0], goal = 0

Output: 15

Constraints:

  • 1 <= nums.length <= 30,000
  • nums[i] is either 0 or 1.
  • 0 <= goal <= nums.length


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Prefix Sum - Enables O(1) subarray sum queries after O(n) preprocessing
  • Hash Map - Used to count occurrences of prefix sums for efficient lookup
  • Sliding Window / Two Pointers - Alternative approach using "at most K" technique

1. Brute Force

Intuition

The most straightforward approach is to check every possible subarray. For each starting index, we extend the subarray one element at a time, keeping track of the running sum. Whenever the sum equals the goal, we count it. Since the array contains only 0s and 1s, the sum can only increase as we extend the subarray.

Algorithm

  1. Initialize a result counter to 0.
  2. For each starting index i from 0 to n-1, initialize a running sum to 0.
  3. For each ending index j from i to n-1, add nums[j] to the running sum.
  4. If the running sum equals the goal, increment the result counter.
  5. Return the total count.
class Solution:
    def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
        n, res = len(nums), 0

        for i in range(n):
            curSum = 0
            for j in range(i, n):
                curSum += nums[j]
                if curSum == goal:
                    res += 1

        return res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(1)O(1)

2. Prefix Sum + Hash Map

Intuition

For a subarray from index i+1 to j to have sum equal to goal, we need prefixSum[j] - prefixSum[i] = goal, which means prefixSum[i] = prefixSum[j] - goal. As we iterate through the array computing prefix sums, we can use a hash map to count how many times each prefix sum has occurred. For each position, we check how many previous positions had a prefix sum that would give us our target subarray sum.

Algorithm

  1. Initialize a hash map with {0: 1} to handle subarrays starting from index 0.
  2. Initialize prefix sum and result counter to 0.
  3. For each element, add it to the prefix sum.
  4. Look up prefixSum - goal in the hash map and add its count to the result.
  5. Increment the count of the current prefix sum in the hash map.
  6. Return the total count.
class Solution:
    def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
        prefixSum = 0
        count = { 0 : 1 } # prefixSum -> count
        res = 0

        for num in nums:
            prefixSum += num
            res += count.get(prefixSum - goal, 0)
            count[prefixSum] = count.get(prefixSum, 0) + 1

        return res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

3. Prefix Sum + Array

Intuition

Since the array contains only 0s and 1s, the prefix sum at any position is at most n (the array length). This allows us to use an array instead of a hash map for counting prefix sums. Array access is faster than hash map operations, making this approach more efficient in practice. The logic remains the same as the hash map approach.

Algorithm

  1. Create an array of size n+1 to count prefix sums, initialized to 0.
  2. Set count[0] = 1 to handle subarrays starting from index 0.
  3. Initialize prefix sum and result counter to 0.
  4. For each element, add it to the prefix sum.
  5. If prefixSum >= goal, add count[prefixSum - goal] to the result.
  6. Increment count[prefixSum].
  7. Return the total count.
class Solution:
    def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
        n = len(nums)
        count = [0] * (n + 1)
        count[0] = 1
        prefixSum, res = 0, 0

        for num in nums:
            prefixSum += num
            if prefixSum >= goal:
                res += count[prefixSum - goal]
            count[prefixSum] += 1

        return res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

4. Sliding Window

Intuition

Counting subarrays with exactly goal sum is tricky with a sliding window because shrinking the window might skip valid subarrays. However, counting subarrays with sum at most goal is straightforward. We can use the identity: count(exactly goal) = count(at most goal) - count(at most goal-1). For each right endpoint, we shrink the left side until the sum is at most the target, and all subarrays ending at right with starting points from left to right are valid.

Algorithm

  1. Define a helper function that counts subarrays with sum at most x.
  2. In the helper, use two pointers (left and right) with a running sum.
  3. For each right, add nums[right] to the sum. While sum > x, shrink from the left.
  4. Add (right - left + 1) to the count, representing all valid subarrays ending at right.
  5. Return helper(goal) - helper(goal - 1) as the final answer.
class Solution:
    def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
        def helper(x):
            if x < 0:
                return 0
            res = l = cur = 0
            for r in range(len(nums)):
                cur += nums[r]
                while cur > x:
                    cur -= nums[l]
                    l += 1
                res += (r - l + 1)
            return res

        return helper(goal) - helper(goal - 1)

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1)

Common Pitfalls

Forgetting to Initialize Prefix Sum Count with Zero

The hash map must start with {0: 1} to count subarrays that start from index 0. Without this, subarrays where the prefix sum exactly equals the goal are missed.

# Wrong: count = {}
# Correct: count = {0: 1}

Not Handling goal = 0 in Sliding Window

When using the sliding window approach, helper(goal - 1) becomes helper(-1). Without the early return for negative values, the window logic breaks and may produce incorrect results or infinite loops.

# Wrong: missing the x < 0 check
# Correct: if x < 0: return 0

Breaking Early When Sum Exceeds Goal

In the brute force approach, some incorrectly add a break when curSum > goal. This is wrong because the array contains only 0s and 1s, and adding more 0s keeps the sum unchanged, potentially finding more valid subarrays.