Facebook Pixel

2031. Count Subarrays With More Ones Than Zeros 🔒

Problem Description

You are given a binary array nums that contains only integers 0 and 1. Your task is to find the total number of subarrays where the count of 1s is greater than the count of 0s.

A subarray is defined as a contiguous sequence of elements within the array. For example, if nums = [1, 0, 1, 1], then [1, 0, 1] and [0, 1, 1] are subarrays, but [1, 1, 1] (skipping the 0) is not.

Since the answer can be very large, you need to return the result modulo 10^9 + 7.

Example breakdown:

  • If we have a subarray [1, 0, 1, 1], it contains three 1s and one 0, so the count of 1s (3) is greater than the count of 0s (1). This subarray would be counted.
  • If we have a subarray [0, 0, 1], it contains one 1 and two 0s, so the count of 1s (1) is not greater than the count of 0s (2). This subarray would not be counted.

The challenge is to efficiently count all such valid subarrays across all possible subarrays of the given array.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to transform this counting problem into something more manageable. Instead of directly counting 1s and 0s in every subarray, we can use a clever transformation: treat each 0 as -1 and keep 1 as 1. Now the problem becomes: count subarrays where the sum is greater than 0.

Why does this transformation help? With this change, a subarray has more 1s than 0s if and only if its sum is positive. For example, [1, 0, 1, 1] becomes [1, -1, 1, 1] with sum 2 > 0, indicating more 1s than 0s.

To count subarrays with positive sum, we use prefix sums. If prefix[i] is the sum of elements from index 0 to i, then the sum of subarray from index j+1 to i equals prefix[i] - prefix[j]. We want this difference to be positive, meaning prefix[i] > prefix[j].

So for each position i, we need to count how many previous positions j (where j < i) have prefix[j] < prefix[i]. This is essentially asking: "How many prefix sums before position i are smaller than the current prefix sum?"

This "count how many values are less than X" problem is perfectly suited for a Binary Indexed Tree (Fenwick Tree), which can efficiently:

  1. Query the count of values in a range
  2. Update counts as we process new elements

We process the array from left to right, maintaining the frequency of each prefix sum seen so far. For each new position, we query how many smaller prefix sums exist (these form valid subarrays ending at current position), then add the current prefix sum to our data structure.

The base offset in the code handles negative prefix sums by shifting all values to be positive, since Binary Indexed Trees typically work with positive indices.

Learn more about Segment Tree, Binary Search, Divide and Conquer and Merge Sort patterns.

Solution Approach

The solution uses Prefix Sum combined with a Binary Indexed Tree (Fenwick Tree) to efficiently count subarrays.

Step 1: Transform the Problem

  • Convert each 0 to -1 in the calculation (not modifying the original array)
  • This is done with: s += x or -1 where x or -1 returns 1 if x=1, and -1 if x=0
  • Now we need to count subarrays with sum > 0

Step 2: Initialize Data Structures

  • Create a Binary Indexed Tree with size n + base where base = n + 1
  • The base offset ensures all indices are positive (handling negative prefix sums)
  • Initialize the tree with prefix sum 0 having count 1 by calling tree.update(base, 1)
  • This represents the empty prefix before any elements

Step 3: Process Each Element

  • Maintain running prefix sum s and answer counter ans
  • For each element in nums:
    1. Update prefix sum: s += x or -1
    2. Query how many previous prefix sums are less than current s:
      • Call tree.query(s - 1 + base) to get count of all prefix sums < s
      • These form valid subarrays ending at current position
    3. Add this count to ans (with modulo operation)
    4. Update the tree to include current prefix sum: tree.update(s + base, 1)

Binary Indexed Tree Operations:

  • Update(x, v): Adds value v to position x
    • Uses bit manipulation x += x & -x to traverse parent nodes
    • Time complexity: O(log n)
  • Query(x): Returns sum of values from position 1 to x
    • Uses bit manipulation x -= x & -x to traverse the tree
    • Time complexity: O(log n)

Why This Works:

  • At position i, we've seen prefix sums from all positions 0 to i-1
  • Querying for prefix sums less than s gives us all valid starting positions for subarrays ending at i
  • Each such position j creates a subarray [j+1...i] with positive sum

Time Complexity: O(n log n) - we perform O(log n) operations for each of the n elements Space Complexity: O(n) - for the Binary Indexed Tree

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [1, 0, 1, 0].

Step 1: Transform to +1/-1

  • Original: [1, 0, 1, 0]
  • Transform 0s to -1s: [1, -1, 1, -1]

Step 2: Initialize

  • Create BIT with size n + base = 4 + 5 = 9 (base = n + 1 = 5)
  • Initialize with prefix sum 0: tree.update(5, 1) (0 + base = 5)
  • Set s = 0 (running prefix sum), ans = 0 (result counter)

Step 3: Process each element

Position 0: nums[0] = 1

  • Update prefix sum: s = 0 + 1 = 1
  • Query: How many prefix sums < 1?
    • tree.query(1 - 1 + 5) = tree.query(5) returns 1
    • This means 1 subarray ending here has positive sum: [1]
  • Update answer: ans = 0 + 1 = 1
  • Update tree: tree.update(1 + 5, 1) to record prefix sum 1

Position 1: nums[1] = 0 (transformed to -1)

  • Update prefix sum: s = 1 + (-1) = 0
  • Query: How many prefix sums < 0?
    • tree.query(0 - 1 + 5) = tree.query(4) returns 0
    • No subarrays ending here have positive sum
  • Update answer: ans = 1 + 0 = 1
  • Update tree: tree.update(0 + 5, 1) to record prefix sum 0

Position 2: nums[2] = 1

  • Update prefix sum: s = 0 + 1 = 1
  • Query: How many prefix sums < 1?
    • tree.query(1 - 1 + 5) = tree.query(5) returns 2
    • Two subarrays ending here have positive sum:
      • From after prefix 0 at start: [1, 0, 1] (sum = 1)
      • From after prefix 0 at position 1: [1] (sum = 1)
  • Update answer: ans = 1 + 2 = 3
  • Update tree: tree.update(1 + 5, 1) to record prefix sum 1

Position 3: nums[3] = 0 (transformed to -1)

  • Update prefix sum: s = 1 + (-1) = 0
  • Query: How many prefix sums < 0?
    • tree.query(0 - 1 + 5) = tree.query(4) returns 0
    • No subarrays ending here have positive sum
  • Update answer: ans = 3 + 0 = 3
  • Update tree: tree.update(0 + 5, 1) to record prefix sum 0

Final Answer: 3

The valid subarrays are:

  1. [1] at position 0 (1 > 0)
  2. [1] at position 2 (1 > 0)
  3. [1, 0, 1] from positions 0-2 (2 ones > 1 zero)

Solution Implementation

1from typing import List
2
3
4class BinaryIndexedTree:
5    """Fenwick Tree / Binary Indexed Tree for efficient prefix sum queries and updates."""
6  
7    __slots__ = ["size", "tree"]
8  
9    def __init__(self, size: int) -> None:
10        """
11        Initialize a Binary Indexed Tree with given size.
12      
13        Args:
14            size: The maximum index that can be used (1-indexed internally)
15        """
16        self.size = size
17        self.tree = [0] * (size + 1)  # 1-indexed array for BIT
18  
19    def update(self, index: int, delta: int) -> None:
20        """
21        Add a value to the element at given index.
22      
23        Args:
24            index: Position to update (1-indexed)
25            delta: Value to add to the current element
26        """
27        while index <= self.size:
28            self.tree[index] += delta
29            # Move to next index by adding the least significant bit
30            index += index & -index
31  
32    def query(self, index: int) -> int:
33        """
34        Get the prefix sum from index 1 to given index.
35      
36        Args:
37            index: End position for prefix sum (1-indexed)
38          
39        Returns:
40            Sum of elements from position 1 to index
41        """
42        prefix_sum = 0
43        while index > 0:
44            prefix_sum += self.tree[index]
45            # Move to parent by removing the least significant bit
46            index -= index & -index
47        return prefix_sum
48
49
50class Solution:
51    def subarraysWithMoreZerosThanOnes(self, nums: List[int]) -> int:
52        """
53        Count the number of subarrays where zeros outnumber ones.
54      
55        The algorithm transforms the problem:
56        - Replace 0 with -1 and keep 1 as 1
57        - A subarray has more 0s than 1s if its sum is negative
58        - Use prefix sums and count valid subarrays ending at each position
59      
60        Args:
61            nums: Binary array containing only 0s and 1s
62          
63        Returns:
64            Count of subarrays with more zeros than ones, modulo 10^9 + 7
65        """
66        n = len(nums)
67      
68        # Offset to handle negative indices (prefix sums can be negative)
69        offset = n + 1
70      
71        # Initialize BIT with size to accommodate all possible prefix sum values
72        fenwick_tree = BinaryIndexedTree(n + offset)
73      
74        # Add initial state: empty prefix has sum 0
75        fenwick_tree.update(offset, 1)
76      
77        MOD = 10**9 + 7
78        result = 0
79        prefix_sum = 0
80      
81        for num in nums:
82            # Transform: 0 becomes -1, 1 stays 1
83            prefix_sum += 1 if num else -1
84          
85            # Count all previous prefix sums that are less than current
86            # This gives us subarrays ending here with negative sum
87            count_valid_subarrays = fenwick_tree.query(prefix_sum - 1 + offset)
88            result = (result + count_valid_subarrays) % MOD
89          
90            # Record current prefix sum for future subarrays
91            fenwick_tree.update(prefix_sum + offset, 1)
92      
93        return result
94
1/**
2 * Binary Indexed Tree (Fenwick Tree) for efficient prefix sum queries and updates
3 */
4class BinaryIndexedTree {
5    private int size;
6    private int[] tree;
7
8    /**
9     * Initialize the Binary Indexed Tree with given size
10     * @param n the maximum index that can be used (1-indexed)
11     */
12    public BinaryIndexedTree(int n) {
13        this.size = n;
14        this.tree = new int[n + 1];  // 1-indexed array
15    }
16
17    /**
18     * Add value v to position x and update all affected ranges
19     * @param x the position to update (1-indexed)
20     * @param v the value to add
21     */
22    public void update(int x, int v) {
23        // Traverse all positions that include index x in their range
24        // x & -x gives the lowest set bit (LSB)
25        for (; x <= size; x += x & -x) {
26            tree[x] += v;
27        }
28    }
29
30    /**
31     * Query the prefix sum from index 1 to x
32     * @param x the end position of the range (1-indexed)
33     * @return the sum of elements from 1 to x
34     */
35    public int query(int x) {
36        int sum = 0;
37        // Traverse the tree by removing the lowest set bit each time
38        for (; x > 0; x -= x & -x) {
39            sum += tree[x];
40        }
41        return sum;
42    }
43}
44
45/**
46 * Solution to find the number of subarrays with more 1s than 0s
47 */
48class Solution {
49    /**
50     * Count subarrays where the number of 1s exceeds the number of 0s
51     * @param nums the input array containing 0s and 1s
52     * @return the count of valid subarrays modulo 10^9 + 7
53     */
54    public int subarraysWithMoreZerosThanOnes(int[] nums) {
55        int n = nums.length;
56      
57        // Offset to handle negative prefix sums (make all indices positive)
58        int offset = n + 1;
59      
60        // Initialize BIT with size to accommodate all possible prefix sum values
61        BinaryIndexedTree fenwickTree = new BinaryIndexedTree(n + offset);
62      
63        // Add initial state: empty prefix with sum 0
64        fenwickTree.update(offset, 1);
65      
66        final int MOD = (int) 1e9 + 7;
67        int result = 0;
68        int prefixSum = 0;  // Running prefix sum (1s contribute +1, 0s contribute -1)
69      
70        for (int num : nums) {
71            // Transform: 1 -> +1, 0 -> -1
72            prefixSum += (num == 0) ? -1 : 1;
73          
74            // Count all previous prefix sums that are less than current prefix sum
75            // This gives us subarrays ending at current position with positive sum
76            result += fenwickTree.query(prefixSum - 1 + offset);
77            result %= MOD;
78          
79            // Add current prefix sum to the tree for future queries
80            fenwickTree.update(prefixSum + offset, 1);
81        }
82      
83        return result;
84    }
85}
86
1class BinaryIndexedTree {
2private:
3    int size;                    // Size of the array (1-indexed)
4    vector<int> tree;            // Fenwick tree array
5  
6public:
7    // Constructor: Initialize BIT with given size
8    BinaryIndexedTree(int n) : size(n), tree(n + 1, 0) {}
9  
10    // Update: Add value v to index x
11    void update(int x, int v) {
12        // Traverse all affected nodes in the tree
13        for (; x <= size; x += x & -x) {
14            tree[x] += v;
15        }
16    }
17  
18    // Query: Get prefix sum from index 1 to x
19    int query(int x) {
20        int sum = 0;
21        // Traverse parents to calculate prefix sum
22        for (; x > 0; x -= x & -x) {
23            sum += tree[x];
24        }
25        return sum;
26    }
27};
28
29class Solution {
30public:
31    int subarraysWithMoreZerosThanOnes(vector<int>& nums) {
32        int n = nums.size();
33      
34        // Offset to handle negative prefix sums (make all indices positive)
35        int offset = n + 1;
36      
37        // BIT to track frequency of prefix sums seen so far
38        BinaryIndexedTree fenwickTree(n + offset);
39      
40        // Initially, we have seen prefix sum 0 once (empty prefix)
41        fenwickTree.update(offset, 1);
42      
43        const int MOD = 1e9 + 7;
44        int answer = 0;
45        int prefixSum = 0;  // Running prefix sum (1 for ones, -1 for zeros)
46      
47        for (int num : nums) {
48            // Convert: 0 -> -1, 1 -> 1
49            prefixSum += (num == 0) ? -1 : 1;
50          
51            // Count subarrays ending at current position with more 1s than 0s
52            // We need previous prefix sums < current prefix sum
53            answer += fenwickTree.query(prefixSum - 1 + offset);
54            answer %= MOD;
55          
56            // Add current prefix sum to the tree
57            fenwickTree.update(prefixSum + offset, 1);
58        }
59      
60        return answer;
61    }
62};
63
1// Binary Indexed Tree (Fenwick Tree) for efficient prefix sum queries and updates
2let treeSize: number;
3let fenwickTree: number[];
4
5/**
6 * Initializes the Binary Indexed Tree with given size
7 * @param n - The size of the tree
8 */
9function initializeBIT(n: number): void {
10    treeSize = n;
11    fenwickTree = Array(n + 1).fill(0);
12}
13
14/**
15 * Updates the value at index x by adding v
16 * Uses the BIT update pattern: move to next index by adding the lowest set bit
17 * @param x - The index to update (1-indexed)
18 * @param v - The value to add at index x
19 */
20function update(x: number, v: number): void {
21    while (x <= treeSize) {
22        fenwickTree[x] += v;
23        x += x & -x; // Add the lowest set bit to move to next index
24    }
25}
26
27/**
28 * Queries the prefix sum from index 1 to x
29 * Uses the BIT query pattern: move to parent by removing the lowest set bit
30 * @param x - The index to query up to (1-indexed)
31 * @returns The prefix sum from 1 to x
32 */
33function query(x: number): number {
34    let sum = 0;
35    while (x > 0) {
36        sum += fenwickTree[x];
37        x -= x & -x; // Remove the lowest set bit to move to parent
38    }
39    return sum;
40}
41
42/**
43 * Counts subarrays where the number of 1s exceeds the number of 0s
44 * Uses prefix sum technique: transforms 0s to -1s and 1s stay as 1s
45 * A subarray has more 1s than 0s if its sum is positive
46 * @param nums - Array of 0s and 1s
47 * @returns The count of valid subarrays modulo 10^9 + 7
48 */
49function subarraysWithMoreZerosThanOnes(nums: number[]): number {
50    const arrayLength: number = nums.length;
51    // Offset to handle negative prefix sums (make all indices positive)
52    const indexOffset: number = arrayLength + 1;
53  
54    // Initialize BIT with size to accommodate all possible prefix sums
55    initializeBIT(arrayLength + indexOffset);
56  
57    // Add initial state: empty prefix with sum 0
58    update(indexOffset, 1);
59  
60    const MOD: number = 1e9 + 7;
61    let result: number = 0;
62    let prefixSum: number = 0;
63  
64    for (const value of nums) {
65        // Transform: 0 becomes -1, 1 stays 1
66        prefixSum += value || -1;
67      
68        // Count all previous prefix sums less than current prefixSum
69        // These form subarrays with positive sum (more 1s than 0s)
70        result += query(prefixSum - 1 + indexOffset);
71        result %= MOD;
72      
73        // Record current prefix sum for future subarrays
74        update(prefixSum + indexOffset, 1);
75    }
76  
77    return result;
78}
79

Time and Space Complexity

Time Complexity: O(n × log n)

The main solution iterates through the array once with n iterations. For each element in the array:

  • The query operation on the Binary Indexed Tree takes O(log n) time, as it traverses up the tree by repeatedly subtracting the least significant bit (x -= x & -x), which can happen at most log n times
  • The update operation also takes O(log n) time, as it traverses up the tree by repeatedly adding the least significant bit (x += x & -x), which can happen at most log n times

Since we perform both query and update operations for each of the n elements, the overall time complexity is O(n × log n).

Space Complexity: O(n)

The space usage consists of:

  • The Binary Indexed Tree's array c with size n + base + 1, where base = n + 1, resulting in approximately 2n + 2 elements, which is O(n)
  • A few constant variables (base, mod, ans, s) which take O(1) space

Therefore, the total space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Problem Interpretation

The code provided actually solves for subarrays with more zeros than ones, but the problem statement asks for subarrays with more ones than zeros. This is a critical mismatch.

The Issue:

  • The transformation prefix_sum += 1 if num else -1 makes zeros contribute -1 and ones contribute +1
  • Querying for prefix sums less than current (fenwick_tree.query(prefix_sum - 1 + offset)) counts subarrays with negative sum
  • Negative sum means more zeros than ones, not more ones than zeros

Solution: Change the transformation to make zeros contribute +1 and ones contribute -1, or change the query logic:

# Option 1: Flip the transformation
prefix_sum += -1 if num else 1  # Now 1 becomes -1, 0 becomes +1

# Option 2: Keep transformation but query differently
# Count prefix sums greater than current (requires range query)
# This is more complex with basic BIT

2. Offset Calculation Confusion

The offset is used to handle negative prefix sums, but it's easy to miscalculate the required range.

The Issue:

  • With n elements, prefix sum can range from -n to +n
  • Current offset of n + 1 may not be sufficient for all cases
  • Incorrect offset can cause array index out of bounds or incorrect counting

Solution: Use a safer offset calculation:

# Maximum possible negative prefix sum is -n (all zeros)
# Maximum possible positive prefix sum is +n (all ones)
offset = n + 1  # This ensures all indices are positive

3. Forgetting Initial State

Not initializing the BIT with the empty prefix can lead to missing valid subarrays that start from index 0.

The Issue:

  • Without fenwick_tree.update(offset, 1), subarrays starting from the beginning aren't counted
  • For example, if the first element gives a valid subarray [0:1], it won't be counted

Solution: Always initialize with the empty prefix:

# This represents prefix sum of 0 before processing any elements
fenwick_tree.update(offset, 1)

4. Modulo Operation Timing

Applying modulo only at the end or forgetting it during intermediate steps can cause integer overflow in languages with fixed integer sizes.

The Issue:

  • While Python handles arbitrary precision integers, forgetting modulo can still impact performance
  • In other languages, this would cause overflow

Solution: Apply modulo after each addition:

result = (result + count_valid_subarrays) % MOD

Corrected Solution for "More Ones than Zeros":

def subarraysWithMoreOnesThanZeros(self, nums: List[int]) -> int:
    n = len(nums)
    offset = n + 1
    fenwick_tree = BinaryIndexedTree(2 * n + 1)  # Ensure sufficient size
  
    # Initialize with empty prefix
    fenwick_tree.update(offset, 1)
  
    MOD = 10**9 + 7
    result = 0
    prefix_sum = 0
  
    for num in nums:
        # Transform: 1 stays 1, 0 becomes -1
        prefix_sum += 1 if num else -1
      
        # Count all previous prefix sums that are LESS than current
        # For positive sum subarrays (more 1s than 0s)
        count_valid = fenwick_tree.query(prefix_sum - 1 + offset)
        result = (result + count_valid) % MOD
      
        # Record current prefix sum
        fenwick_tree.update(prefix_sum + offset, 1)
  
    return result
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More