Facebook Pixel

927. Three Equal Parts

Problem Description

You have an array arr containing only 0s and 1s. Your task is to divide this array into three non-empty parts where each part represents the same binary value.

Given an array, you need to find two indices [i, j] where i + 1 < j such that:

  • The first part: arr[0], arr[1], ..., arr[i]
  • The second part: arr[i + 1], arr[i + 2], ..., arr[j - 1]
  • The third part: arr[j], arr[j + 1], ..., arr[arr.length - 1]

All three parts must have equal binary values when interpreted as binary numbers.

Important notes:

  • Each part is interpreted as a complete binary number. For example, [1,1,0] represents 6 in decimal (110 in binary = 6), not 3.
  • Leading zeros are allowed, meaning [0,1,1] and [1,1] both represent the value 3 in decimal.
  • If it's impossible to divide the array into three equal parts, return [-1, -1].
  • If a valid division exists, return any valid [i, j] pair.

Example considerations:

  • If the array is [1,0,1,0,1], one valid division could make all three parts equal to the binary value 5 (binary 101).
  • If the array is all zeros like [0,0,0,0,0,0], any division would work since all parts would equal 0.
  • The division must create exactly three non-empty parts - you cannot have empty parts.
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that for three parts to represent the same binary value, they must have the same number of 1s. This is because the positions and count of 1s fundamentally determine a binary number's value.

Let's think about what makes binary numbers equal:

  • They must have the same number of 1s (since each 1 contributes to the value)
  • The pattern of 1s and 0s must match when we ignore leading zeros

First, we count the total number of 1s in the array. If this count isn't divisible by 3, it's impossible to split the array into three equal parts - we return [-1, -1] immediately.

If the count is 0 (all zeros), any split works since 0 == 0 == 0, so we can return [0, n-1].

For the general case, each part must contain exactly cnt/3 ones. Here's the clever part: we can find where each part should approximately start by locating:

  • The 1st one (start of first part's significant digits)
  • The (cnt/3 + 1)-th one (start of second part's significant digits)
  • The (2*cnt/3 + 1)-th one (start of third part's significant digits)

Once we have these starting positions i, j, and k, we need to verify that the three parts are actually equal. We do this by moving through all three parts simultaneously, comparing corresponding positions. If at any point arr[i] != arr[j] or arr[j] != arr[k], the parts aren't equal.

The traversal continues until k reaches the end of the array. If we successfully reach the end with all corresponding elements matching, we've found our answer: the first part ends at i-1, and the third part starts at j.

This approach works because:

  1. We ensure each part has the correct number of 1s
  2. We verify the pattern matches by parallel traversal
  3. Leading zeros are naturally handled since we start comparing from the first 1 in each part

Learn more about Math patterns.

Solution Approach

The implementation follows a systematic approach using counting and three pointers:

Step 1: Count and Validate

First, we count the total number of 1s in the array and check if it's divisible by 3:

cnt, mod = divmod(sum(arr), 3)
  • If mod != 0, the array cannot be split into three equal parts, return [-1, -1]
  • If cnt == 0 (all zeros), return [0, n-1] since any split works

Step 2: Find Starting Positions

We use a helper function find(x) to locate the index of the x-th 1 in the array:

def find(x):
    s = 0
    for i, v in enumerate(arr):
        s += v
        if s == x:
            return i

Using this function, we find three critical positions:

  • i = find(1): Position of the 1st one (start of first part's significant bits)
  • j = find(cnt + 1): Position of the (cnt + 1)-th one (start of second part's significant bits)
  • k = find(cnt * 2 + 1): Position of the (2*cnt + 1)-th one (start of third part's significant bits)

Step 3: Parallel Traversal and Verification

Starting from positions i, j, and k, we traverse all three parts simultaneously:

while k < n and arr[i] == arr[j] == arr[k]:
    i, j, k = i + 1, j + 1, k + 1

This loop continues as long as:

  • k hasn't reached the end of the array
  • The corresponding elements in all three parts are equal

Step 4: Return Result

After the traversal:

  • If k == n, we've successfully verified all three parts are equal
    • Return [i - 1, j] where:
      • First part: arr[0...i-1]
      • Second part: arr[i...j-1]
      • Third part: arr[j...n-1]
  • Otherwise, return [-1, -1] indicating no valid split exists

Example Walkthrough:

For array [1,0,1,0,1,0,1,0,1]:

  1. Total 1s = 5, not divisible by 3 → return [-1, -1]

For array [1,1,0,0,1,1,0,0,1,1]:

  1. Total 1s = 6, each part needs 2 ones
  2. Find positions: i=0, j=4, k=8 (positions of 1st, 3rd, and 5th ones)
  3. Parallel traversal verifies the pattern matches
  4. Return appropriate indices

Time Complexity: O(n) - single pass to count, find positions, and verify Space Complexity: O(1) - only using a few variables

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 the array [1,0,1,0,1]:

Step 1: Count and Validate

  • Count total 1s: We have three 1s in the array
  • Check divisibility: 3 ÷ 3 = 1 with remainder 0 ✓
  • Each part needs exactly 1 one

Step 2: Find Starting Positions

  • Find the 1st one: Position 0 (value = 1)
  • Find the 2nd one (1 + 1): Position 2 (value = 1)
  • Find the 3rd one (2 + 1): Position 4 (value = 1)
  • So i = 0, j = 2, k = 4

Step 3: Parallel Traversal Starting from positions i=0, j=2, k=4:

Stepijkarr[i]arr[j]arr[k]Match?
1024111
213500-k >= n

When k reaches 5 (array length), we stop.

Step 4: Return Result Since k successfully reached the end of the array with all matches:

  • Return [i - 1, j] = [0, 2]

Verification:

  • Part 1: arr[0:1] = [1] → binary value = 1
  • Part 2: arr[1:2] = [0,1] → binary value = 1 (leading zero ignored)
  • Part 3: arr[2:5] = [0,1] → binary value = 1 (leading zero ignored)

All three parts equal 1 ✓


Let's try another example with [0,1,0,0,1,0,0,1]:

Step 1: Count 1s = 3, divisible by 3, each part needs 1 one

Step 2: Find positions:

  • 1st one at index 1: i = 1
  • 2nd one at index 4: j = 4
  • 3rd one at index 7: k = 7

Step 3: Parallel traversal from i=1, j=4, k=7:

Stepijkarr[i]arr[j]arr[k]Match?
1147111
225800-k >= n

Step 4: Return [1, 5]

Verification:

  • Part 1: arr[0:2] = [0,1] → value = 1
  • Part 2: arr[2:5] = [0,0,1] → value = 1
  • Part 3: arr[5:8] = [0,0,1] → value = 1

All parts equal 1 ✓

Solution Implementation

1class Solution:
2    def threeEqualParts(self, arr: List[int]) -> List[int]:
3        """
4        Split binary array into three parts with equal binary values.
5      
6        Args:
7            arr: List of 0s and 1s representing a binary number
8          
9        Returns:
10            List of two indices [i, j] where:
11            - Part 1: arr[0] to arr[i]
12            - Part 2: arr[i+1] to arr[j-1]  
13            - Part 3: arr[j] to arr[n-1]
14            Returns [-1, -1] if no valid split exists
15        """
16      
17        def find_nth_one(target_ones: int) -> int:
18            """
19            Find the index of the nth '1' in the array.
20          
21            Args:
22                target_ones: The target count of 1s to find
23              
24            Returns:
25                Index where cumulative count of 1s equals target_ones
26            """
27            ones_count = 0
28            for index, value in enumerate(arr):
29                ones_count += value
30                if ones_count == target_ones:
31                    return index
32      
33        # Get array length and total count of 1s
34        array_length = len(arr)
35        total_ones = sum(arr)
36      
37        # Check if total 1s can be evenly divided into 3 parts
38        ones_per_part, remainder = divmod(total_ones, 3)
39      
40        # If not divisible by 3, no valid split exists
41        if remainder != 0:
42            return [-1, -1]
43      
44        # Special case: if all zeros, any split works
45        if ones_per_part == 0:
46            return [0, array_length - 1]
47      
48        # Find starting positions of first '1' in each part
49        # first_start: index of 1st '1' in part 1
50        # second_start: index of 1st '1' in part 2 (after ones_per_part 1s)
51        # third_start: index of 1st '1' in part 3 (after 2*ones_per_part 1s)
52        first_start = find_nth_one(1)
53        second_start = find_nth_one(ones_per_part + 1)
54        third_start = find_nth_one(ones_per_part * 2 + 1)
55      
56        # Compare the three parts digit by digit from their first '1'
57        # All three parts must match exactly to be equal binary numbers
58        while third_start < array_length and \
59              arr[first_start] == arr[second_start] == arr[third_start]:
60            first_start += 1
61            second_start += 1
62            third_start += 1
63      
64        # If we compared all digits successfully (third_start reached end),
65        # return the split indices, otherwise no valid split exists
66        if third_start == array_length:
67            return [first_start - 1, second_start]
68        else:
69            return [-1, -1]
70
1class Solution {
2    private int[] array;
3
4    /**
5     * Splits the binary array into three parts with equal binary values.
6     * @param arr The input binary array containing only 0s and 1s
7     * @return An array [i, j] where arr[0..i], arr[i+1..j-1], and arr[j..n-1] represent the same binary value,
8     *         or [-1, -1] if no such split exists
9     */
10    public int[] threeEqualParts(int[] arr) {
11        this.array = arr;
12        int arrayLength = arr.length;
13      
14        // Count total number of 1s in the array
15        int totalOnes = 0;
16        for (int value : arr) {
17            totalOnes += value;
18        }
19      
20        // If total 1s cannot be divided equally into 3 parts, return failure
21        if (totalOnes % 3 != 0) {
22            return new int[] {-1, -1};
23        }
24      
25        // Special case: if all elements are 0, any valid split works
26        if (totalOnes == 0) {
27            return new int[] {0, arrayLength - 1};
28        }
29      
30        // Each part should have this many 1s
31        int onesPerPart = totalOnes / 3;
32      
33        // Find the starting position of each part based on the count of 1s
34        // firstPartStart: index where the 1st one appears
35        // secondPartStart: index where the (onesPerPart + 1)th one appears
36        // thirdPartStart: index where the (2 * onesPerPart + 1)th one appears
37        int firstPartStart = findIndexOfNthOne(1);
38        int secondPartStart = findIndexOfNthOne(onesPerPart + 1);
39        int thirdPartStart = findIndexOfNthOne(onesPerPart * 2 + 1);
40      
41        // Compare the three parts digit by digit from their starting positions
42        // All three parts must match exactly
43        while (thirdPartStart < arrayLength && 
44               array[firstPartStart] == array[secondPartStart] && 
45               array[secondPartStart] == array[thirdPartStart]) {
46            firstPartStart++;
47            secondPartStart++;
48            thirdPartStart++;
49        }
50      
51        // If we've successfully compared all elements up to the end of array,
52        // the split is valid. Return the boundaries.
53        // Otherwise, no valid split exists.
54        if (thirdPartStart == arrayLength) {
55            return new int[] {firstPartStart - 1, secondPartStart};
56        } else {
57            return new int[] {-1, -1};
58        }
59    }
60  
61    /**
62     * Finds the index of the nth occurrence of 1 in the array.
63     * @param targetOccurrence The occurrence number of 1 to find (1-indexed)
64     * @return The index where the nth 1 appears
65     */
66    private int findIndexOfNthOne(int targetOccurrence) {
67        int onesCount = 0;
68        for (int index = 0; index < array.length; index++) {
69            onesCount += array[index];
70            if (onesCount == targetOccurrence) {
71                return index;
72            }
73        }
74        return 0;  // This should not happen if targetOccurrence is valid
75    }
76}
77
1class Solution {
2public:
3    vector<int> threeEqualParts(vector<int>& arr) {
4        int arraySize = arr.size();
5      
6        // Count total number of 1s in the array
7        int totalOnes = accumulate(arr.begin(), arr.end(), 0);
8      
9        // If total 1s cannot be divided by 3, impossible to split equally
10        if (totalOnes % 3 != 0) {
11            return {-1, -1};
12        }
13      
14        // Special case: if all zeros, any split works
15        if (totalOnes == 0) {
16            return {0, arraySize - 1};
17        }
18      
19        // Each part should have this many 1s
20        int onesPerPart = totalOnes / 3;
21      
22        // Lambda function to find the index where we've seen exactly x ones
23        auto findIndexWithXOnes = [&](int targetOnes) {
24            int onesCount = 0;
25            for (int index = 0; index < arraySize; ++index) {
26                onesCount += arr[index];
27                if (onesCount == targetOnes) {
28                    return index;
29                }
30            }
31            return 0;
32        };
33      
34        // Find starting positions of significant bits in each part
35        // firstPartStart: index where first 1 appears
36        // secondPartStart: index where (onesPerPart + 1)th 1 appears
37        // thirdPartStart: index where (2 * onesPerPart + 1)th 1 appears
38        int firstPartStart = findIndexWithXOnes(1);
39        int secondPartStart = findIndexWithXOnes(onesPerPart + 1);
40        int thirdPartStart = findIndexWithXOnes(2 * onesPerPart + 1);
41      
42        // Compare the three parts digit by digit
43        // They should be identical from their starting positions
44        while (thirdPartStart < arraySize && 
45               arr[firstPartStart] == arr[secondPartStart] && 
46               arr[secondPartStart] == arr[thirdPartStart]) {
47            ++firstPartStart;
48            ++secondPartStart;
49            ++thirdPartStart;
50        }
51      
52        // If we successfully compared all digits (reached end of array),
53        // return the split points; otherwise return failure
54        if (thirdPartStart == arraySize) {
55            return {firstPartStart - 1, secondPartStart};
56        } else {
57            return {-1, -1};
58        }
59    }
60};
61
1/**
2 * Finds three equal parts in a binary array where each part represents the same binary number
3 * @param arr - Binary array containing only 0s and 1s
4 * @returns Array with two indices [i, j] where arr[0..i], arr[i+1..j], arr[j+1..n-1] represent equal binary numbers
5 *          Returns [-1, -1] if it's impossible to split into three equal parts
6 */
7function threeEqualParts(arr: number[]): number[] {
8    /**
9     * Finds the index where the sum of elements from start equals the target value
10     * @param targetSum - The target cumulative sum to find
11     * @returns Index where the cumulative sum equals targetSum
12     */
13    function findIndexWithSum(targetSum: number): number {
14        let cumulativeSum: number = 0;
15        for (let index: number = 0; index < arrayLength; ++index) {
16            cumulativeSum += arr[index];
17            if (cumulativeSum === targetSum) {
18                return index;
19            }
20        }
21        return 0;
22    }
23  
24    const arrayLength: number = arr.length;
25  
26    // Count total number of 1s in the array
27    let totalOnes: number = 0;
28    for (const value of arr) {
29        totalOnes += value;
30    }
31  
32    // If total 1s cannot be divided equally into 3 parts, return impossible
33    if (totalOnes % 3 !== 0) {
34        return [-1, -1];
35    }
36  
37    // Special case: if all elements are 0, any valid split works
38    if (totalOnes === 0) {
39        return [0, arrayLength - 1];
40    }
41  
42    // Each part should have exactly onesPerPart number of 1s
43    const onesPerPart: number = Math.floor(totalOnes / 3);
44  
45    // Find starting indices of each part's first 1
46    const firstPartStart: number = findIndexWithSum(1);
47    const secondPartStart: number = findIndexWithSum(onesPerPart + 1);
48    const thirdPartStart: number = findIndexWithSum(onesPerPart * 2 + 1);
49  
50    // Compare the three parts element by element starting from their first 1
51    let i: number = firstPartStart;
52    let j: number = secondPartStart;
53    let k: number = thirdPartStart;
54  
55    while (k < arrayLength && arr[i] === arr[j] && arr[j] === arr[k]) {
56        i++;
57        j++;
58        k++;
59    }
60  
61    // If we've successfully compared all elements until the end, return valid split indices
62    // Otherwise, the parts are not equal
63    return k === arrayLength ? [i - 1, j] : [-1, -1];
64}
65

Time and Space Complexity

Time Complexity: O(n)

The algorithm performs the following operations:

  • sum(arr): Iterates through the array once - O(n)
  • find(x) function: Called 3 times, each iterating through the array in worst case - 3 * O(n) = O(n)
  • The while loop at the end: Iterates through remaining elements after position k, which is at most n - k elements - O(n) in worst case

Since all operations are sequential and not nested, the overall time complexity is O(n) + O(n) + O(n) = O(n).

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space:

  • Variables n, cnt, mod: O(1)
  • Variables i, j, k: O(1)
  • Variable s inside the find function: O(1)

No additional data structures that scale with input size are created, so the space complexity is O(1).

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

Common Pitfalls

1. Misunderstanding Binary Value Equality vs String Equality

Pitfall: A common mistake is thinking that the three parts need to be identical strings of 0s and 1s, rather than having equal binary values. For example, thinking [0,1,1] and [1,1] are different, when they both represent the decimal value 3.

Why it happens: The problem requires comparing binary VALUES, not binary STRINGS. Leading zeros don't affect the value.

Solution: The algorithm correctly handles this by:

  • Finding the first '1' in each part (skipping leading zeros)
  • Comparing from these starting positions onward
  • This ensures [0,0,1,0,1] and [1,0,1] are recognized as equal (both are 5)

2. Incorrect Handling of All-Zeros Case

Pitfall: When the array contains only zeros, the find_nth_one() function would return None since it never finds a '1', causing the code to crash when trying to use these positions.

Why it happens: The special case of all zeros needs different handling since there are no '1's to find and any split is valid.

Solution: The code handles this correctly by:

if ones_per_part == 0:  # All zeros case
    return [0, array_length - 1]

This returns immediately before attempting to find any '1's.

3. Off-by-One Errors in Index Calculation

Pitfall: Confusion about whether indices are inclusive or exclusive, especially when returning [i-1, j] instead of [i, j].

Why it happens: The problem requires:

  • Part 1: arr[0...i] (inclusive)
  • Part 2: arr[i+1...j-1] (inclusive)
  • Part 3: arr[j...n-1] (inclusive)

After the while loop, first_start has moved one position past the last matching element, so we need to return [first_start - 1, second_start].

Solution: Carefully track what each pointer represents:

  • During comparison: pointers track current comparison position
  • After comparison: first_start - 1 gives the correct end of Part 1
  • second_start gives the correct start of Part 3

4. Not Validating Trailing Zeros in the Third Part

Pitfall: The algorithm must ensure the third part ends exactly at the array's end. If there are different numbers of trailing zeros in each part, they won't be equal.

Example: Array [1,0,0,1,0,1] cannot be split into three equal parts because the trailing zeros would differ.

Solution: The code handles this by:

while third_start < array_length and \
      arr[first_start] == arr[second_start] == arr[third_start]:

The condition third_start == array_length after the loop ensures we've compared ALL elements, including any trailing zeros in the third part.

5. Integer Overflow in Very Large Arrays

Pitfall: While not an issue in Python, in other languages, interpreting very long binary strings as integers could cause overflow.

Solution: The algorithm avoids this by never actually converting the binary parts to integers. Instead, it compares them digit-by-digit, which works regardless of the array size.

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

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

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

Load More