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 1
s is greater than the count of 0
s.
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 three1
s and one0
, so the count of1
s (3) is greater than the count of0
s (1). This subarray would be counted. - If we have a subarray
[0, 0, 1]
, it contains one1
and two0
s, so the count of1
s (1) is not greater than the count of0
s (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.
Intuition
The key insight is to transform this counting problem into something more manageable. Instead of directly counting 1
s and 0
s 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 1
s than 0
s 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 1
s than 0
s.
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:
- Query the count of values in a range
- 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
wherex or -1
returns1
ifx=1
, and-1
ifx=0
- Now we need to count subarrays with sum > 0
Step 2: Initialize Data Structures
- Create a Binary Indexed Tree with size
n + base
wherebase = n + 1
- The
base
offset ensures all indices are positive (handling negative prefix sums) - Initialize the tree with prefix sum
0
having count1
by callingtree.update(base, 1)
- This represents the empty prefix before any elements
Step 3: Process Each Element
- Maintain running prefix sum
s
and answer counterans
- For each element in
nums
:- Update prefix sum:
s += x or -1
- 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
- Call
- Add this count to
ans
(with modulo operation) - Update the tree to include current prefix sum:
tree.update(s + base, 1)
- Update prefix sum:
Binary Indexed Tree Operations:
- Update(x, v): Adds value
v
to positionx
- Uses bit manipulation
x += x & -x
to traverse parent nodes - Time complexity:
O(log n)
- Uses bit manipulation
- 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)
- Uses bit manipulation
Why This Works:
- At position
i
, we've seen prefix sums from all positions0
toi-1
- Querying for prefix sums less than
s
gives us all valid starting positions for subarrays ending ati
- 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 EvaluatorExample 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)
- From after prefix 0 at start:
- 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]
at position 0 (1 > 0)[1]
at position 2 (1 > 0)[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 takesO(log n)
time, as it traverses up the tree by repeatedly subtracting the least significant bit (x -= x & -x
), which can happen at mostlog n
times - The
update
operation also takesO(log n)
time, as it traverses up the tree by repeatedly adding the least significant bit (x += x & -x
), which can happen at mostlog 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 sizen + base + 1
, wherebase = n + 1
, resulting in approximately2n + 2
elements, which isO(n)
- A few constant variables (
base
,mod
,ans
,s
) which takeO(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
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Want a Structured Path to Master System Design Too? Don’t Miss This!