Number of Zero-Filled Subarrays visualization

2348: Number of Zero-Filled Subarrays

Difficulty: Medium
Topics: Array, Math, Counting
Companies: Google, Amazon, Microsoft
Pro Tip: The key insight is that for a consecutive sequence of k zeros, the number of zero-filled subarrays ending at the current position is equal to the length of the current consecutive zeros.

Problem Statement

Given an integer array nums, return the number of subarrays filled with 0. A subarray is a contiguous non-empty sequence of elements within an array.

Example 1

Input: nums = [1,3,0,0,2,0,0,4]

Output: 6

Explanation: There are 4 occurrences of [0] as a subarray. There are 2 occurrences of [0,0] as a subarray.

Example 2

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

Output: 9

Explanation: 5 occurrences of [0], 3 occurrences of [0,0], and 1 occurrence of [0,0,0].

Example 3

Input: nums = [2,10,2019]

Output: 0

Explanation: There is no subarray filled with 0.

Problem Link: View on LeetCode ↗

Key Insight

The solution relies on these crucial observations:

  1. For a consecutive sequence of k zeros, the number of zero-filled subarrays is k*(k+1)/2
  2. We can count subarrays ending at each position by maintaining a running count of consecutive zeros
  3. Each time we encounter a zero, the number of subarrays ending at that position is equal to the current consecutive zero count
  4. This approach allows us to solve the problem in a single pass with constant space
Important: The constraint 1 ≤ nums.length ≤ 10^5 requires an O(n) time solution for efficiency.

Approach 1: Consecutive Zero Counting (Optimal)

Traverse the array while maintaining a count of consecutive zeros. For each zero encountered, increment the count and add it to the result.

Algorithm Steps

  1. Initialize result = 0 and consecutiveZeros = 0
  2. Iterate through each element in the array:
    • If the element is zero, increment consecutiveZeros and add it to result
    • If the element is non-zero, reset consecutiveZeros to 0
  3. Return the result

Complexity Analysis

Time Complexity Space Complexity
O(n) O(1)

Linear time since we traverse the array once, constant space as we only use a few variables.

Consecutive Zero Counting
class Solution {
public:
    long long zeroFilledSubarray(vector<int>& nums) {
        long long ans = 0;
        int count = 0;
        
        for (int num : nums) {
            if (num == 0) {
                count++;
                ans += count;
            } else {
                count = 0;
            }
        }
        
        return ans;
    }
};
class Solution {
    public long zeroFilledSubarray(int[] nums) {
        long ans = 0;
        int count = 0;
        
        for (int num : nums) {
            if (num == 0) {
                count++;
                ans += count;
            } else {
                count = 0;
            }
        }
        
        return ans;
    }
}
class Solution:
    def zeroFilledSubarray(self, nums: List[int]) -> int:
        ans = 0
        count = 0
        
        for num in nums:
            if num == 0:
                count += 1
                ans += count
            else:
                count = 0
                
        return ans
Efficiency: This approach runs in O(n) time with O(1) space, making it optimal for the problem constraints.

Approach 2: Mathematical Formula

Identify all contiguous segments of zeros and apply the formula n*(n+1)/2 for each segment.

Algorithm Steps

  1. Initialize result = 0 and currentLength = 0
  2. Iterate through the array:
    • If element is zero, increment currentLength
    • If element is non-zero and currentLength > 0, add currentLength*(currentLength+1)/2 to result and reset currentLength
  3. After the loop, add any remaining segment of zeros
  4. Return the result

Complexity Analysis

Time Complexity Space Complexity
O(n) O(1)
Mathematical Formula
class Solution {
public:
    long long zeroFilledSubarray(vector<int>& nums) {
        long long result = 0;
        int currentLength = 0;
        
        for (int num : nums) {
            if (num == 0) {
                currentLength++;
            } else {
                if (currentLength > 0) {
                    result += (long)currentLength * (currentLength + 1) / 2;
                    currentLength = 0;
                }
            }
        }
        
        // Handle any remaining zeros at the end
        if (currentLength > 0) {
            result += (long)currentLength * (currentLength + 1) / 2;
        }
        
        return result;
    }
};
class Solution {
    public long zeroFilledSubarray(int[] nums) {
        long result = 0;
        int currentLength = 0;
        
        for (int num : nums) {
            if (num == 0) {
                currentLength++;
            } else {
                if (currentLength > 0) {
                    result += (long)currentLength * (currentLength + 1) / 2;
                    currentLength = 0;
                }
            }
        }
        
        // Handle any remaining zeros at the end
        if (currentLength > 0) {
            result += (long)currentLength * (currentLength + 1) / 2;
        }
        
        return result;
    }
}
class Solution:
    def zeroFilledSubarray(self, nums: List[int]) -> int:
        result = 0
        current_length = 0
        
        for num in nums:
            if num == 0:
                current_length += 1
            else:
                if current_length > 0:
                    result += current_length * (current_length + 1) // 2
                    current_length = 0
                    
        # Handle any remaining zeros at the end
        if current_length > 0:
            result += current_length * (current_length + 1) // 2
            
        return result
Advantage: This approach explicitly shows the mathematical formula being applied, making it easier to understand the underlying pattern.

Approach 3: Two Pointers

Use two pointers to identify contiguous segments of zeros and calculate subarrays for each segment.

Algorithm Steps

  1. Initialize result = 0 and left = 0
  2. Iterate right pointer from 0 to n-1:
    • If nums[right] != 0, move left to right + 1
    • If nums[right] == 0, add (right - left + 1) to result
  3. Return the result

Complexity Analysis

Time Complexity Space Complexity
O(n) O(1)
Two Pointers Approach
class Solution {
public:
    long long zeroFilledSubarray(vector<int>& nums) {
        long long result = 0;
        int left = 0;
        
        for (int right = 0; right < nums.size(); right++) {
            if (nums[right] != 0) {
                left = right + 1;
            } else {
                result += (right - left + 1);
            }
        }
        
        return result;
    }
};
class Solution {
    public long zeroFilledSubarray(int[] nums) {
        long result = 0;
        int left = 0;
        
        for (int right = 0; right < nums.length; right++) {
            if (nums[right] != 0) {
                left = right + 1;
            } else {
                result += (right - left + 1);
            }
        }
        
        return result;
    }
}
class Solution:
    def zeroFilledSubarray(self, nums: List[int]) -> int:
        result = 0
        left = 0
        
        for right in range(len(nums)):
            if nums[right] != 0:
                left = right + 1
            else:
                result += (right - left + 1)
                
        return result
Pattern Recognition: This approach uses the sliding window technique to identify contiguous segments of zeros, which is a common pattern for subarray problems.

Edge Cases and Testing

1. All Zeros

Input: [0,0,0,0,0] → Output: 15
Explanation: 5 single zeros, 4 pairs, 3 triplets, 2 quadruplets, 1 quintuplet
5+4+3+2+1 = 15

2. No Zeros

Input: [1,2,3,4,5] → Output: 0

3. Mixed Zeros and Non-Zeros

Input: [0,1,0,0,2,0,0,0] → Output: 9
Explanation: Segments: [0] (1), [0,0] (3), [0,0,0] (6) → 1+3+6 = 10
But careful: Actually [0], [0], [0,0], [0], [0,0], [0,0,0] → 1+1+3+1+3+6 = 15? Wait let's recalc:
The zeros are at indices: 0, 2, 3, 5, 6, 7
Subarrays: 
[0] (1)
[2], [2,3] (1+2=3)
[5], [5,6], [5,6,7] (1+2+3=6)
[6], [6,7] (1+2=3)
[7] (1)
Total: 1+3+6+3+1 = 14? 

Actually the correct calculation:
For position 0: 1 subarray
For position 2: 1 subarray ([2])
For position 3: 2 subarrays ([3], [2,3])
For position 5: 1 subarray ([5])
For position 6: 2 subarrays ([6], [5,6])
For position 7: 3 subarrays ([7], [6,7], [5,6,7])
Total: 1+1+2+1+2+3 = 10

The example output is 9, so let me double-check the problem statement.
Actually, the problem says:
Input: [0,0,0,2,0,0] → Output: 9
So for [0,1,0,0,2,0,0,0]:
Zeros at indices: 0, 2, 3, 5, 6, 7
We have segments: 
- Segment 1: index 0 → 1 subarray
- Segment 2: indices 2-3 → length 2 → 3 subarrays
- Segment 3: indices 5-7 → length 3 → 6 subarrays
Total: 1 + 3 + 6 = 10

But the example says output should be 9 for [0,0,0,2,0,0] which has:
- Segment 1: indices 0-2 → length 3 → 6 subarrays
- Segment 2: indices 4-5 → length 2 → 3 subarrays
Total: 6 + 3 = 9

So for [0,1,0,0,2,0,0,0] we have 1 + 3 + 6 = 10

4. Single Zero

Input: [0] → Output: 1

5. Zeros at Beginning and End

Input: [0,0,1,2,0,3,0,0] → Output: 6
Explanation: Segments: [0,0] (3), [0] (1), [0,0] (3) → 3+1+3 = 7? 
Wait, let's calculate properly:
Indices: 0-1 (length 2 → 3), index 4 (length 1 → 1), indices 6-7 (length 2 → 3)
Total: 3 + 1 + 3 = 7
Important: Always test with various patterns of zeros to ensure the algorithm handles all cases correctly.

Detailed Explanation

The problem requires counting all contiguous subarrays that contain only zeros. The key insight is recognizing that:

0
0
0
5
0
0

For a contiguous segment of k zeros, the number of zero-filled subarrays is given by the formula k*(k+1)/2. This is because:

This sums to k + (k-1) + ... + 1 = k*(k+1)/2.

The optimal solution (Approach 1) uses a clever observation: when we encounter a zero, the number of new subarrays ending at this position is equal to the current length of consecutive zeros. This avoids needing to calculate the formula explicitly for each segment.

For example, consider the array [0, 0, 0]:

This approach efficiently accumulates the result in a single pass with constant space, making it optimal for the problem constraints.

Why it works: Each new zero in a contiguous segment creates new subarrays ending at that position, with the count equal to the current segment length.

FAQs

1. Why does the formula k*(k+1)/2 work for counting subarrays?

For a contiguous segment of k zeros, the number of subarrays is the sum of the first k natural numbers: 1 + 2 + 3 + ... + k = k*(k+1)/2. This counts all possible contiguous subarrays within the segment.

2. What is the time complexity of the optimal solution?

The optimal solution runs in O(n) time, where n is the length of the input array, as it processes each element exactly once.

3. What is the space complexity of the optimal solution?

The space complexity is O(1) as it only uses a constant number of variables regardless of the input size.

4. How does the optimal approach avoid double-counting subarrays?

The optimal approach counts subarrays ending at each position exactly once. Since each subarray has a unique ending position, there's no double-counting.

5. Can this problem be solved using prefix sums?

Yes, but it would be less efficient. You could create a prefix sum array of zeros and then use two loops to find all subarrays with sum zero, but this would be O(n²) time.

6. What is the advantage of Approach 1 over Approach 2?

Approach 1 is more efficient as it doesn't require explicit calculation of the formula for each segment. It accumulates the result in a single pass without needing to reset and calculate segment by segment.

7. How would you handle very large arrays?

The optimal solution already handles large arrays efficiently with O(n) time and O(1) space. For extremely large arrays (billions of elements), the solution would still be efficient.

8. Can this solution be extended to count subarrays with other specific values?

Yes, the same approach can be used to count subarrays filled with any specific value by changing the condition from num == 0 to num == target.

9. What if the array contains negative numbers?

The problem states that the array contains integers, which can be negative. However, we're only counting subarrays filled with zeros, so negative numbers are treated the same as positive non-zero numbers - they reset the consecutive zero count.

10. How does the two-pointer approach work?

The two-pointer approach maintains a left pointer that marks the start of the current contiguous segment of zeros. For each zero, the number of subarrays ending at that position is (right - left + 1).