Partition Array Into Three Parts With Equal Sum
EasyPrompt
Given an array of integers arr, return true if we can partition the array into three non-empty parts with equal sums.
Formally, we can partition the array if we can find indexes i + 1 < j with (arr[0] + arr[1] + ... + arr[i] == arr[i + 1] + arr[i + 2] + ... + arr[j - 1] == arr[j] + arr[j + 1] + ... + arr[arr.length - 1])
Example 1:
Input: arr = [0,2,1,-6,6,-7,9,1,2,0,1]
Output: true
Explanation: 0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1Example 2:
Input: arr = [0,2,1,-6,6,7,9,-1,2,0,1]
Output: falseExample 3:
Input: arr = [3,3,6,5,-2,2,5,1,-9,4]
Output: true
Explanation: 3 + 3 = 6 = 5 - 2 + 2 + 5 + 1 - 9 + 4
Constraints:
3 <= arr.length <= 5 * 104-104 <= arr[i] <= 104
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves checking every possible pair of split points (i, j) to partition the array into three parts. To optimize the calculation of subarray sums, we can pre-calculate a prefix sum array. We iterate through all valid i and j and check if the three resulting subarrays have equal sums.
Algorithm
- Create a prefix sum array
PwhereP[k]stores the sum of elements fromarr[0]toarr[k-1]. - Iterate through the first possible split point
ifrom0toarr.length - 3. - For each
i, iterate through the second possible split pointjfromi + 2toarr.length - 1. - The condition
i + 1 < jensures that all three parts are non-empty. - For each pair
(i, j), calculate the sums of the three parts using the prefix sum array:- Part 1:
sum1 = P[i + 1] - Part 2:
sum2 = P[j] - P[i + 1] - Part 3:
sum3 = P[arr.length] - P[j]
- Part 1:
- If
sum1,sum2, andsum3are all equal, a valid partition is found, so returntrue. - If the loops complete without finding such a partition, return
false.
Walkthrough
The brute-force method systematically checks every possible way to split the array into three non-empty parts. A partition is defined by two indices, i and j, which mark the end of the first part and the start of the third part, respectively. The condition i + 1 < j ensures that the middle part is also non-empty.
To avoid re-calculating the sum of subarrays repeatedly, which would be very inefficient (O(N^3)), we can pre-compute a prefix sum array. The prefix sum array, let's call it prefix, stores the cumulative sum up to each index. Specifically, prefix[k] will store the sum of arr[0]...arr[k-1]. With this, the sum of any subarray arr[a...b] can be found in O(1) time by computing prefix[b+1] - prefix[a].
After building the prefix sum array, we use two nested loops to iterate through all valid pairs of (i, j) and check if sum(part1) == sum(part2) == sum(part3).
class Solution { public boolean canThreePartsEqualSum(int[] arr) { int n = arr.length; int[] prefixSum = new int[n + 1]; for (int i = 0; i < n; i++) { prefixSum[i + 1] = prefixSum[i] + arr[i]; } // i is the end of the first part, j-1 is the end of the second part for (int i = 0; i < n - 2; i++) { for (int j = i + 2; j < n; j++) { int sum1 = prefixSum[i + 1]; int sum2 = prefixSum[j] - prefixSum[i + 1]; int sum3 = prefixSum[n] - prefixSum[j]; if (sum1 == sum2 && sum2 == sum3) { return true; } } } return false; }}Complexity
Time
O(N^2), where N is the length of the array. We have two nested loops to iterate through all possible split points `i` and `j`. The prefix sum calculation takes O(N), but it's dominated by the O(N^2) loops.
Space
O(N) to store the prefix sum array.
Trade-offs
Pros
Conceptually simple and easy to understand.
Correctly explores all possible partitions.
Cons
Inefficient due to nested loops, leading to a quadratic time complexity.
Requires extra space for the prefix sum array.
This approach improves upon brute force by avoiding nested loops. A key observation is that if a valid partition exists, the total sum of the array must be divisible by 3. We can find the first possible end i for the first part (from the left) and the first possible start j for the third part (from the right). If such i and j exist and are separated by at least one element (i.e., i + 1 < j), we have found a valid partition.
Algorithm
- First, calculate the total sum
Sof all elements in the array. - If
Sis not divisible by 3, it's impossible to partition the array into three equal sum parts. Returnfalse. - Calculate the required sum for each part,
target = S / 3. - Find the first index
ifrom the left such that the sum ofarr[0...i]istarget. Let this befirstCutIndex. - Find the first index
jfrom the right (i.e., last index) such that the sum ofarr[j...n-1]istarget. Let this bethirdCutIndex. - If either
firstCutIndexorthirdCutIndexis not found, returnfalse. - A valid partition requires three non-empty parts. This is satisfied if
firstCutIndex < thirdCutIndex - 1. - If this condition is met, return
true; otherwise, returnfalse.
Walkthrough
This method leverages the fact that if the array can be partitioned, each of the three parts must sum to totalSum / 3.
First, we compute the total sum of the array. If it's not divisible by 3, we immediately know a partition is impossible. Otherwise, we calculate the target sum for each part.
Next, we perform two linear scans:
- A forward scan from the beginning of the array to find the first index
iwhere the prefix sumarr[0] + ... + arr[i]equals thetargetsum. - A backward scan from the end of the array to find the first index
jwhere the suffix sumarr[j] + ... + arr[n-1]equals thetargetsum.
If we successfully find both such an i and a j, we then need to check if the partitions are valid. The partitions are arr[0...i], arr[i+1...j-1], and arr[j...n-1]. For all three to be non-empty, we must satisfy the condition i + 1 < j. If this holds, the middle part's sum is guaranteed to also be target, and we've found a solution.
class Solution { public boolean canThreePartsEqualSum(int[] arr) { int totalSum = 0; for (int num : arr) { totalSum += num; } if (totalSum % 3 != 0) { return false; } int target = totalSum / 3; int firstCutIndex = -1; int currentSum = 0; for (int i = 0; i < arr.length; i++) { currentSum += arr[i]; if (currentSum == target) { firstCutIndex = i; break; } } if (firstCutIndex == -1) { return false; } int thirdCutIndex = -1; currentSum = 0; for (int j = arr.length - 1; j >= 0; j--) { currentSum += arr[j]; if (currentSum == target) { thirdCutIndex = j; break; } } if (thirdCutIndex == -1) { return false; } // Check if the parts are non-empty and distinct return firstCutIndex < thirdCutIndex - 1; }}Complexity
Time
O(N), where N is the length of the array. It involves at most two full passes over the array to find the cut points.
Space
O(1), as it only uses a few variables for sums and indices.
Trade-offs
Pros
Efficient with O(N) time complexity.
Intuitive logic that directly maps to finding the three parts from the ends of the array.
Cons
Requires two passes over the array, which is slightly less efficient than a true single-pass solution.
The logic finds only the first possible cuts from either end, which might not be intuitive but works for this specific problem.
The most optimal approach involves a single pass through the array. After confirming the total sum is divisible by 3 and calculating the target sum for each part, we can iterate through the array once. We maintain a running sum and count how many times this sum equals the target value, effectively forming partitions greedily from the left.
Algorithm
- First, calculate the total sum
Sof all elements in the array. - If
Sis not divisible by 3, it's impossible to partition the array. Returnfalse. - Calculate the required sum for each part,
target = S / 3. - Initialize a counter for the number of parts found,
partsFound = 0, and a running sum,currentSum = 0. - Iterate through the array from index
i = 0toarr.length - 1. - In each iteration, add
arr[i]tocurrentSum. - If
currentSumbecomes equal totarget, we have found a valid partition. IncrementpartsFoundand resetcurrentSumto 0. - After the loop, if
partsFoundis 3 or more, it means we have successfully partitioned the array. Returntrue. The condition>= 3correctly handles cases wheretarget = 0, which might lead to more than 3 zero-sum partitions. IftotalSum != 0,partsFoundwill be exactly 3 if a solution exists and the entire array sum is consumed.
Walkthrough
This approach is both efficient and elegant. It relies on the same initial check: if the totalSum of the array is not divisible by 3, partitioning is impossible. If it is, each part must sum to target = totalSum / 3.
The core of the algorithm is a single loop through the array. We maintain a currentSum and a counter partsFound. As we iterate, we accumulate the currentSum. Whenever currentSum equals the target, we have successfully identified a complete part. At this point, we increment partsFound and reset currentSum to 0 to start accumulating the sum for the next part.
After the loop finishes, we check if we have found at least 3 parts. If partsFound >= 3, it means we were able to find at least two partitions summing to target before reaching the end of the array. This guarantees that the third part is non-empty and, because the total sum is 3 * target, the third part's sum is also target. The condition >= 3 is used instead of == 3 to correctly handle the edge case where target is 0, as there could be multiple consecutive zero-sum partitions.
class Solution { public boolean canThreePartsEqualSum(int[] arr) { int totalSum = 0; for (int num : arr) { totalSum += num; } if (totalSum % 3 != 0) { return false; } int target = totalSum / 3; int partsFound = 0; int currentSum = 0; for (int i = 0; i < arr.length; i++) { currentSum += arr[i]; if (currentSum == target) { partsFound++; currentSum = 0; } } // If totalSum is 0, we could have more than 3 parts of sum 0. // e.g., [0,0,0,0]. target=0, partsFound=4. Valid partition: [0], [0], [0,0] // If totalSum is not 0, we must find exactly 3 parts that consume the whole array. // The check `partsFound >= 3` covers both cases because if we find 2 parts // and there's a non-empty remainder, the partition is valid. return partsFound >= 3; }}Complexity
Time
O(N), where N is the length of the array. We iterate through the array once to calculate the total sum and once more to find the partitions.
Space
O(1), as we only use a few variables to store the sums and counters.
Trade-offs
Pros
Highly efficient with a linear time complexity.
Uses constant extra space.
Simple and elegant logic implemented in a single pass.
Cons
The logic might seem non-obvious at first, especially the
partsFound >= 3condition for thetarget = 0case.
Solutions
Solution
class Solution {public boolean canThreePartsEqualSum(int[] arr) { int s = 0; for (int v : arr) { s += v; } if (s % 3 != 0) { return false; } int i = 0, j = arr.length - 1; int a = 0, b = 0; while (i < arr.length) { a += arr[i]; if (a == s / 3) { break; } ++i; } while (j >= 0) { b += arr[j]; if (b == s / 3) { break; } --j; } return i < j - 1; }}class Solution {public: bool canThreePartsEqualSum(vector<int> &arr) { int s = 0; for (int v : arr) s += v; if (s % 3) return false; int i = 0, j = arr.size() - 1; int a = 0, b = 0; while (i < arr.size()) { a += arr[i]; if (a == s / 3) { break; } ++i; } while (~j) { b += arr[j]; if (b == s / 3) { break; } --j; } return i < j - 1; }};class Solution: def canThreePartsEqualSum(self, arr: List[int]) -> bool: s = sum(arr) if s % 3 != 0: return False i, j = 0, len(arr) - 1 a = b = 0 while i < len(arr): a += arr[i] if a == s // 3: break i += 1 while ~ j: b += arr[j] if b == s // 3: break j -= 1 return i < j - 1Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.