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.
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:
- We ensure each part has the correct number of 1s
- We verify the pattern matches by parallel traversal
- 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]
- First part:
- Return
- Otherwise, return
[-1, -1]
indicating no valid split exists
Example Walkthrough:
For array [1,0,1,0,1,0,1,0,1]
:
- Total 1s = 5, not divisible by 3 → return
[-1, -1]
For array [1,1,0,0,1,1,0,0,1,1]
:
- Total 1s = 6, each part needs 2 ones
- Find positions:
i=0
,j=4
,k=8
(positions of 1st, 3rd, and 5th ones) - Parallel traversal verifies the pattern matches
- 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 EvaluatorExample 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:
Step | i | j | k | arr[i] | arr[j] | arr[k] | Match? |
---|---|---|---|---|---|---|---|
1 | 0 | 2 | 4 | 1 | 1 | 1 | ✓ |
2 | 1 | 3 | 5 | 0 | 0 | - | 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:
Step | i | j | k | arr[i] | arr[j] | arr[k] | Match? |
---|---|---|---|---|---|---|---|
1 | 1 | 4 | 7 | 1 | 1 | 1 | ✓ |
2 | 2 | 5 | 8 | 0 | 0 | - | 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 mostn - 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 thefind
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.
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!