1013. Partition Array Into Three Parts With Equal Sum
Problem Description
You are given an array of integers arr
. Your task is to determine if you can split this array into exactly three non-empty parts where each part has the same sum.
More specifically, you need to find two split points (indices i
and j
) such that:
- The first part includes elements from index
0
toi
(inclusive) - The second part includes elements from index
i+1
toj-1
(inclusive) - The third part includes elements from index
j
to the end of the array (inclusive) - All three parts must have equal sums
The constraint i + 1 < j
ensures that all three parts are non-empty.
For example:
- If
arr = [0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1]
, you can split it into[0, 2, 1]
,[-6, 6, -7, 9, 1]
, and[2, 0, 1]
, where each part sums to3
. So the answer would betrue
. - If
arr = [0, 2, 1, -6, 6, 7, 9, -1, 2, 0, 1]
, no valid three-way partition exists with equal sums, so the answer would befalse
.
Return true
if such a partition is possible, otherwise return false
.
Intuition
To split an array into three parts with equal sums, we first need to think about what the sum of each part should be. If the total sum of the array is S
, and we want three equal parts, then each part must have a sum of S/3
. This immediately tells us that if S
is not divisible by 3, it's impossible to create three equal parts.
Once we know each part should sum to S/3
, we can traverse the array and keep a running sum. Every time our running sum reaches S/3
, we've found one complete part. We then reset our running sum to 0 and continue looking for the next part.
The key insight is that we don't need to track the exact positions where we split the array. We just need to count how many times we can form a part with sum S/3
. If we can form at least 3 such parts, then we can definitely partition the array into exactly 3 parts with equal sums.
Why "at least 3" instead of "exactly 3"? Consider an array like [0, 1, -1, 0]
where the total sum is 0, so each part should sum to 0. We might find more than 3 points where the running sum equals 0, but as long as we have at least 3, we can choose any two of them as our split points to create exactly 3 parts.
This greedy approach works because once we've accumulated a sum of S/3
, we can "commit" to ending a part there. We don't need to consider other possibilities because all valid partitions must have parts summing to S/3
.
Learn more about Greedy patterns.
Solution Approach
The implementation follows a straightforward traversal and summation strategy:
-
Calculate Total Sum and Check Divisibility: First, we compute the sum of all elements in the array and check if it's divisible by 3 using
divmod(sum(arr), 3)
. This gives us both the quotients
(which represents the target sum for each part) and the remaindermod
. Ifmod
is non-zero, we immediately returnfalse
since it's impossible to split into three equal parts. -
Initialize Tracking Variables: We use two variables:
cnt
: Counts how many complete parts with sums
we've foundt
: Tracks the running sum of the current part being formed
-
Traverse and Count Parts: We iterate through each element
x
in the array:- Add
x
to the running sumt
- If
t
equalss
, we've found a complete part:- Increment
cnt
by 1 - Reset
t
to 0 to start tracking the next part
- Increment
- Add
-
Check Result: After traversing the entire array, we return
true
ifcnt >= 3
, otherwisefalse
.
The algorithm works because:
- Every time we reach the target sum
s
, we greedily mark it as the end of a part - We don't need to track exact split positions - just counting occurrences is sufficient
- Having at least 3 parts with sum
s
guarantees we can choose appropriate split points to create exactly 3 parts - The last element is automatically included in the count since after processing all elements, if we've found at least 2 parts of sum
s
, the remaining elements must also sum tos
(since total =3s
)
Time Complexity: O(n)
where n
is the length of the array (one pass through the array)
Space Complexity: O(1)
as we only use a few variables regardless of input size
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a small example: arr = [0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1]
Step 1: Calculate total sum and check divisibility
- Total sum = 0 + 2 + 1 + (-6) + 6 + (-7) + 9 + 1 + 2 + 0 + 1 = 9
- Using
divmod(9, 3)
: quotients = 3
, remaindermod = 0
- Since remainder is 0, we can proceed (each part should sum to 3)
Step 2: Initialize tracking variables
cnt = 0
(number of complete parts found)t = 0
(running sum of current part)
Step 3: Traverse the array and count parts
Index | Element | Running Sum t | Action | Count cnt |
---|---|---|---|---|
0 | 0 | 0 + 0 = 0 | Continue | 0 |
1 | 2 | 0 + 2 = 2 | Continue | 0 |
2 | 1 | 2 + 1 = 3 | Found part! Reset t | 1 |
3 | -6 | 0 + (-6) = -6 | Continue | 1 |
4 | 6 | -6 + 6 = 0 | Continue | 1 |
5 | -7 | 0 + (-7) = -7 | Continue | 1 |
6 | 9 | -7 + 9 = 2 | Continue | 1 |
7 | 1 | 2 + 1 = 3 | Found part! Reset t | 2 |
8 | 2 | 0 + 2 = 2 | Continue | 2 |
9 | 0 | 2 + 0 = 2 | Continue | 2 |
10 | 1 | 2 + 1 = 3 | Found part! Reset t | 3 |
Step 4: Check result
cnt = 3
, which is >= 3- Return
true
The algorithm found 3 complete parts with sum 3:
- Part 1: [0, 2, 1] = 3
- Part 2: [-6, 6, -7, 9, 1] = 3
- Part 3: [2, 0, 1] = 3
Note that the algorithm doesn't track the exact split positions, but by counting 3 occurrences of the target sum, it confirms that a valid 3-way partition exists.
Solution Implementation
1class Solution:
2 def canThreePartsEqualSum(self, arr: List[int]) -> bool:
3 # Calculate the total sum and check if it's divisible by 3
4 target_sum, remainder = divmod(sum(arr), 3)
5
6 # If the total sum is not divisible by 3, we can't partition into 3 equal parts
7 if remainder != 0:
8 return False
9
10 # Count how many partitions with the target sum we can find
11 partition_count = 0
12 current_sum = 0
13
14 # Iterate through the array to find partitions
15 for num in arr:
16 current_sum += num
17
18 # When we reach the target sum, we found a valid partition
19 if current_sum == target_sum:
20 partition_count += 1
21 current_sum = 0 # Reset for the next partition
22
23 # We need at least 3 partitions (the last partition is implicit)
24 # If we found at least 2 partitions explicitly, the remaining elements
25 # automatically form the third partition with the same sum
26 return partition_count >= 3
27
1class Solution {
2 public boolean canThreePartsEqualSum(int[] arr) {
3 // Calculate the total sum of the array
4 int totalSum = Arrays.stream(arr).sum();
5
6 // If total sum is not divisible by 3, we cannot split into 3 equal parts
7 if (totalSum % 3 != 0) {
8 return false;
9 }
10
11 // Calculate the target sum for each part
12 int targetSum = totalSum / 3;
13
14 // Count the number of parts found with the target sum
15 int partCount = 0;
16 int currentSum = 0;
17
18 // Iterate through the array to find parts with target sum
19 for (int num : arr) {
20 currentSum += num;
21
22 // When we find a part with target sum
23 if (currentSum == targetSum) {
24 partCount++;
25 currentSum = 0; // Reset current sum for next part
26 }
27 }
28
29 // We need at least 3 parts (can be more if target sum is 0)
30 // This handles cases where we have extra zero-sum segments
31 return partCount >= 3;
32 }
33}
34
1class Solution {
2public:
3 bool canThreePartsEqualSum(vector<int>& arr) {
4 // Calculate the total sum of the array
5 int totalSum = accumulate(arr.begin(), arr.end(), 0);
6
7 // If total sum is not divisible by 3, we cannot partition into 3 equal parts
8 if (totalSum % 3 != 0) {
9 return false;
10 }
11
12 // Target sum for each partition
13 int targetSum = totalSum / 3;
14
15 // Count of partitions found and current running sum
16 int partitionCount = 0;
17 int currentSum = 0;
18
19 // Iterate through the array to find partitions
20 for (int element : arr) {
21 currentSum += element;
22
23 // When we reach the target sum, we found a partition
24 if (currentSum == targetSum) {
25 currentSum = 0; // Reset for next partition
26 partitionCount++;
27 }
28 }
29
30 // We need at least 3 partitions (the last element forms the third partition)
31 // Using >= 3 handles edge cases where targetSum is 0
32 return partitionCount >= 3;
33 }
34};
35
1/**
2 * Determines if an array can be partitioned into three non-empty parts with equal sum.
3 * @param arr - The input array of numbers to check
4 * @returns true if the array can be partitioned into three equal sum parts, false otherwise
5 */
6function canThreePartsEqualSum(arr: number[]): boolean {
7 // Calculate the total sum of all elements in the array
8 let totalSum: number = arr.reduce((accumulator: number, current: number) => accumulator + current, 0);
9
10 // If total sum is not divisible by 3, we cannot partition into three equal parts
11 if (totalSum % 3 !== 0) {
12 return false;
13 }
14
15 // Calculate the target sum for each partition (using bitwise OR for integer division)
16 const targetSum: number = (totalSum / 3) | 0;
17
18 // Initialize partition counter and running sum
19 let partitionCount: number = 0;
20 let currentSum: number = 0;
21
22 // Iterate through the array to find partitions
23 for (const element of arr) {
24 currentSum += element;
25
26 // When we reach the target sum, we've found a valid partition
27 if (currentSum === targetSum) {
28 partitionCount++;
29 currentSum = 0; // Reset current sum for the next partition
30 }
31 }
32
33 // We need at least 3 partitions for a valid three-way split
34 return partitionCount >= 3;
35}
36
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array arr
. This is because the algorithm performs two linear operations: first calculating the sum of all elements in the array using sum(arr)
which takes O(n)
time, and then iterating through the array once with a for loop to count the partitions, which also takes O(n)
time. The total time complexity is O(n) + O(n) = O(n)
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for variables s
, mod
, cnt
, t
, and x
, regardless of the input array 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
Pitfall: Incorrectly Handling Arrays with Target Sum of 0
The most significant pitfall in this solution is when the target sum equals 0 (i.e., when the total array sum is 0). In this case, the algorithm counts every occurrence where the running sum becomes 0 as a partition boundary, which can lead to incorrect results.
Example of the Problem:
Consider arr = [0, 1, -1, 0]
:
- Total sum = 0, so target_sum = 0
- As we traverse:
- After index 0: current_sum = 0 β partition_count = 1
- After index 2: current_sum = 0 β partition_count = 2
- After index 3: current_sum = 0 β partition_count = 3
- The algorithm returns
true
(partition_count >= 3)
However, this is incorrect! We need exactly 3 non-empty parts. The algorithm is essentially counting 4 partition points (including the implicit end), which would create 4 parts if we tried to reconstruct the actual partitions.
Solution to the Pitfall:
To fix this issue, we need to ensure we're finding exactly 2 explicit partition points (not including the array end), which will create exactly 3 parts:
class Solution:
def canThreePartsEqualSum(self, arr: List[int]) -> bool:
target_sum, remainder = divmod(sum(arr), 3)
if remainder != 0:
return False
partition_count = 0
current_sum = 0
# Important: We iterate until len(arr) - 1 to ensure the last part is non-empty
for i in range(len(arr) - 1):
current_sum += arr[i]
if current_sum == target_sum:
partition_count += 1
current_sum = 0
# Early termination: once we find 2 partitions,
# the remaining elements automatically form the third
if partition_count == 2:
return True
return False
Key Changes:
- Loop until
len(arr) - 1
: This ensures we leave at least one element for the third part - Early termination at partition_count == 2: Once we find 2 parts with the target sum, we know the remaining elements form the third part (which must also sum to target_sum)
- Return False if we don't find exactly 2 partition points: This handles edge cases where we might find more potential partition points than needed
This approach correctly handles all cases, including when target_sum = 0, by ensuring we create exactly 3 non-empty parts.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!