Facebook Pixel

2750. Ways to Split Array Into Good Subarrays

Problem Description

You have a binary array nums containing only 0s and 1s.

A subarray is considered good if it contains exactly one element with value 1.

Your task is to find the total number of ways to split the entire array nums into consecutive good subarrays. Each element must belong to exactly one subarray, and the subarrays must cover the entire array without overlapping.

Since the answer can be very large, return it modulo 10^9 + 7.

For example, if nums = [0,1,0,0,1], one valid split would be [0,1,0] and [0,1], where each subarray contains exactly one 1.

The key insight is that between any two consecutive 1s in the array, you can place a dividing line at any position between them. If two 1s are at indices j and i, there are i - j different positions where you can place the dividing line. The total number of ways to split the array is the product of all such possibilities between consecutive pairs of 1s.

If the array contains no 1s, it's impossible to create any good subarrays, so the answer would be 0.

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

Intuition

Let's think about what makes a valid split. Each subarray must contain exactly one 1. This means that every 1 in the array acts as an "anchor" - it must belong to exactly one subarray, and that subarray cannot contain any other 1.

Consider what happens between two consecutive 1s. The zeros between them can go either with the left 1 or the right 1. For example, if we have [1, 0, 0, 0, 1], we need to decide where to "cut" between these two 1s:

  • We could cut after the first 1: [1] | [0, 0, 0, 1]
  • We could cut after the first zero: [1, 0] | [0, 0, 1]
  • We could cut after the second zero: [1, 0, 0] | [0, 1]
  • We could cut after the third zero: [1, 0, 0, 0] | [1]

Notice that if the two 1s are at positions j and i, we have exactly i - j positions where we can place the cut (any position between j and i).

Now here's the key insight: these decisions are independent. The choice of where to cut between the first and second 1 doesn't affect where we can cut between the second and third 1. This independence means we can use the multiplication principle - the total number of ways is the product of the number of choices at each decision point.

So the algorithm becomes: find all consecutive pairs of 1s, calculate the number of ways to split between each pair (i - j), and multiply all these numbers together. If there are no 1s in the array, return 0 since we can't form any good subarrays.

Learn more about Math and Dynamic Programming patterns.

Solution Approach

Based on the multiplication principle we identified, let's implement the solution step by step:

  1. Initialize variables:

    • ans = 1: This will store our final answer (product of all possibilities)
    • j = -1: This tracks the index of the previous 1 we encountered
    • mod = 10^9 + 7: For taking modulo of the result
  2. Iterate through the array: For each element at index i:

    • If the element is 0, skip it (continue to next iteration)
    • If the element is 1:
      • Check if we've seen a 1 before (i.e., j > -1)
      • If yes, multiply ans by (i - j) which represents the number of ways to split between the previous 1 at index j and current 1 at index i
      • Update j = i to mark the current 1 as the new "previous 1" for the next iteration
  3. Handle edge cases:

    • If j == -1 after the loop, it means we never found any 1 in the array, so return 0
    • Otherwise, return ans

Why this works:

  • Between any two consecutive 1s at positions j and i, we can place the dividing line at any of the i - j positions
  • Each placement decision is independent of others
  • By the multiplication principle, total ways = product of individual possibilities
  • We use modulo operation at each multiplication step to prevent overflow

Time Complexity: O(n) where n is the length of the array (single pass)
Space Complexity: O(1) as we only use a constant amount of extra space

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

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

Step 1: Initialize variables

  • ans = 1 (will store our product)
  • j = -1 (tracks previous 1's index)
  • mod = 10^9 + 7

Step 2: Iterate through the array

  • i = 0: nums[0] = 0 → Skip (it's not a 1)

  • i = 1: nums[1] = 1 → First 1 found!

    • Check if j > -1? No (j = -1), so this is our first 1
    • Update j = 1
    • ans remains 1
  • i = 2: nums[2] = 0 → Skip

  • i = 3: nums[3] = 0 → Skip

  • i = 4: nums[4] = 1 → Second 1 found!

    • Check if j > -1? Yes (j = 1)
    • Calculate ways to split between indices 1 and 4: 4 - 1 = 3
    • This means we can cut at positions 2, 3, or 4:
      • Position 2: [0,1] | [0,0,1,0,1]
      • Position 3: [0,1,0] | [0,1,0,1]
      • Position 4: [0,1,0,0] | [1,0,1]
    • Update ans = 1 × 3 = 3
    • Update j = 4
  • i = 5: nums[5] = 0 → Skip

  • i = 6: nums[6] = 1 → Third 1 found!

    • Check if j > -1? Yes (j = 4)
    • Calculate ways to split between indices 4 and 6: 6 - 4 = 2
    • This means we can cut at positions 5 or 6:
      • Position 5: ...previous... | [1] | [0,1]
      • Position 6: ...previous... | [1,0] | [1]
    • Update ans = 3 × 2 = 6
    • Update j = 6

Step 3: Return result

  • Since j = 6 > -1, we found at least one 1
  • Return ans = 6

The 6 total ways to split the array are:

  1. [0,1] | [0,0,1] | [0,1]
  2. [0,1] | [0,0,1,0] | [1]
  3. [0,1,0] | [0,1] | [0,1]
  4. [0,1,0] | [0,1,0] | [1]
  5. [0,1,0,0] | [1] | [0,1]
  6. [0,1,0,0] | [1,0] | [1]

Each subarray contains exactly one 1, validating our solution!

Solution Implementation

1class Solution:
2    def numberOfGoodSubarraySplits(self, nums: List[int]) -> int:
3        # Define modulo for large number handling
4        MOD = 10**9 + 7
5      
6        # Initialize result and last position of 1
7        result = 1
8        last_one_position = -1
9      
10        # Iterate through the array with index and value
11        for current_index, value in enumerate(nums):
12            # Skip if current element is 0
13            if value == 0:
14                continue
15          
16            # If this is not the first 1 encountered
17            if last_one_position > -1:
18                # Multiply result by the distance between consecutive 1s
19                # This represents the number of ways to split between them
20                result = (result * (current_index - last_one_position)) % MOD
21          
22            # Update the position of the last 1
23            last_one_position = current_index
24      
25        # Return 0 if no 1s found, otherwise return the result
26        return 0 if last_one_position == -1 else result
27
1class Solution {
2    public int numberOfGoodSubarraySplits(int[] nums) {
3        // Define modulo constant for preventing integer overflow
4        final int MODULO = (int) 1e9 + 7;
5      
6        // Initialize result to 1 (multiplicative identity)
7        int result = 1;
8      
9        // Track the index of the previous 1 encountered (-1 means no 1 found yet)
10        int previousOneIndex = -1;
11      
12        // Iterate through the array to find all 1s
13        for (int currentIndex = 0; currentIndex < nums.length; currentIndex++) {
14            // Skip if current element is 0
15            if (nums[currentIndex] == 0) {
16                continue;
17            }
18          
19            // Current element is 1
20            // If this is not the first 1, multiply result by the gap between consecutive 1s
21            if (previousOneIndex > -1) {
22                // The gap between two 1s determines the number of valid split points
23                // Gap = currentIndex - previousOneIndex gives us the number of ways to split
24                long product = (long) result * (currentIndex - previousOneIndex);
25                result = (int) (product % MODULO);
26            }
27          
28            // Update the position of the most recent 1
29            previousOneIndex = currentIndex;
30        }
31      
32        // If no 1 was found in the array, return 0 (no valid splits possible)
33        // Otherwise, return the calculated result
34        return previousOneIndex == -1 ? 0 : result;
35    }
36}
37
1class Solution {
2public:
3    int numberOfGoodSubarraySplits(vector<int>& nums) {
4        // Define modulo constant for preventing integer overflow
5        const int MOD = 1e9 + 7;
6      
7        // Initialize result to 1 (multiplicative identity)
8        // Initialize lastOneIndex to -1 (indicates no 1 has been found yet)
9        int result = 1;
10        int lastOneIndex = -1;
11      
12        // Iterate through the array to find all 1s
13        for (int currentIndex = 0; currentIndex < nums.size(); ++currentIndex) {
14            // Skip if current element is 0
15            if (nums[currentIndex] == 0) {
16                continue;
17            }
18          
19            // If we found a 1 and this is not the first 1
20            if (lastOneIndex > -1) {
21                // Multiply result by the number of ways to split between consecutive 1s
22                // The number of ways equals the distance between current and previous 1
23                // Use 1LL to cast to long long to prevent overflow during multiplication
24                result = static_cast<long long>(result) * (currentIndex - lastOneIndex) % MOD;
25            }
26          
27            // Update the position of the last found 1
28            lastOneIndex = currentIndex;
29        }
30      
31        // If no 1 was found in the array, return 0 (no valid splits possible)
32        // Otherwise, return the calculated result
33        return lastOneIndex == -1 ? 0 : result;
34    }
35};
36
1/**
2 * Counts the number of ways to split an array into good subarrays.
3 * A good subarray must contain at least one 1.
4 * The split positions can only be between consecutive 1s.
5 * 
6 * @param nums - The input binary array containing only 0s and 1s
7 * @returns The number of ways to split the array into good subarrays, modulo 10^9 + 7
8 */
9function numberOfGoodSubarraySplits(nums: number[]): number {
10    // Initialize the result to 1 (base case for multiplication)
11    let result: number = 1;
12  
13    // Track the index of the previous 1 encountered
14    let previousOneIndex: number = -1;
15  
16    // Define the modulo value to prevent integer overflow
17    const MODULO: number = 10 ** 9 + 7;
18  
19    // Get the length of the input array
20    const arrayLength: number = nums.length;
21  
22    // Iterate through each element in the array
23    for (let currentIndex = 0; currentIndex < arrayLength; ++currentIndex) {
24        // Skip if the current element is 0
25        if (nums[currentIndex] === 0) {
26            continue;
27        }
28      
29        // If we found a 1 and there was a previous 1
30        if (previousOneIndex > -1) {
31            // Multiply the result by the number of positions between consecutive 1s
32            // This represents the number of ways to split between these two 1s
33            result = (result * (currentIndex - previousOneIndex)) % MODULO;
34        }
35      
36        // Update the index of the most recent 1
37        previousOneIndex = currentIndex;
38    }
39  
40    // If no 1 was found in the array, return 0 (no valid splits possible)
41    // Otherwise, return the calculated result
42    return previousOneIndex === -1 ? 0 : result;
43}
44

Time and Space Complexity

The time complexity is O(n), where n is the length of the array nums. The algorithm iterates through the array exactly once using the enumerate function, performing constant-time operations (comparisons, arithmetic operations, and modulo operations) for each element.

The space complexity is O(1). The algorithm only uses a fixed amount of extra space for variables mod, ans, j, i, and x, regardless of the input size. No additional data structures that grow with the input size are created.

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

Common Pitfalls

1. Forgetting to Handle the No-1s Case

Pitfall: Not checking if the array contains any 1s at all. If you don't handle this case, the code might return 1 instead of 0 for arrays like [0, 0, 0].

Solution: Always check if last_one_position remains -1 after iteration, which indicates no 1s were found:

return 0 if last_one_position == -1 else result

2. Integer Overflow Without Modulo

Pitfall: Forgetting to apply modulo operation during multiplication. For large arrays with many 1s, the product can exceed integer limits before the final return statement.

Wrong approach:

result = result * (current_index - last_one_position)  # May overflow
# Then applying modulo only at the end
return result % MOD

Correct approach:

result = (result * (current_index - last_one_position)) % MOD  # Apply modulo immediately

3. Off-by-One Error in Distance Calculation

Pitfall: Incorrectly calculating the number of split positions between consecutive 1s. Some might think to use (current_index - last_one_position - 1) or (current_index - last_one_position + 1).

Why i - j is correct: If 1s are at indices 2 and 5, the positions between them are 3, 4, and 5 (right before index 5). That's exactly 5 - 2 = 3 positions.

4. Processing 0s Unnecessarily

Pitfall: Not skipping 0s and performing unnecessary checks or calculations for them, which doesn't affect correctness but impacts efficiency.

Inefficient:

for i, num in enumerate(nums):
    if num == 1:
        # process
    else:
        # unnecessary else block or checks

Efficient:

for i, num in enumerate(nums):
    if num == 0:
        continue  # Skip immediately
    # Process only 1s

5. Using Wrong Initial Value

Pitfall: Initializing result to 0 instead of 1. Since we're multiplying values, starting with 0 would make the final result always 0.

Wrong: result = 0
Correct: result = 1

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

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

Load More