Given an integer array nums
of length at least 7, split it into four non-overlapping subarrays by selecting three indices i
, j
, and k
such that:
0 < i, i + 1 < j, j + 1 < k < nums.length - 1
nums[0] ... nums[i-1]
nums[i+1] ... nums[j-1]
nums[j+1] ... nums[k-1]
nums[k+1] ... nums[nums.length-1]
Find if there exists at least one set of such indices where all four subarrays have the same sum. Each element must only belong to one subarray, and you cannot reuse elements.
Return true
if such a split exists, otherwise return false
.
At first glance, this problem seems to require checking all possible combinations of indices i
, j
, and k
to see if the four resulting subarrays have equal sums. This brute-force approach would be extremely slow, as the number of possible combinations grows rapidly with the size of the array.
To optimize, we realize that if we precompute the prefix sums of the array, we can quickly calculate the sum of any subarray in constant time. We also notice that for a fixed j
, we can efficiently search for valid i
and k
that satisfy the sum condition. By leveraging hash sets to store possible sums for different splits, we can avoid redundant calculations and check for matches efficiently.
This shift from brute-force checking to using prefix sums and hash sets is key to making the solution efficient.
Let's break down the optimized algorithm step by step:
prefix
where prefix[x]
is the sum of nums[0] ... nums[x-1]
.nums[a]...nums[b]
as prefix[b+1] - prefix[a]
in constant time.j
(the middle cut) from 3
to n-4
(to ensure room for the other cuts).j
, iterate i
from 1
to j-2
(so subarrays are non-empty).sums
.j
, iterate k
from j+2
to n-2
.sums
, return true
(a valid split exists).j
, return false
.
We use a hash set to quickly check if a sum found in the right split matches a sum found in the left split for the same j
.
Consider nums = [1, 2, 1, 2, 1, 2, 1]
.
prefix = [0, 1, 3, 4, 6, 7, 9, 10]
j
:
j = 3
(so i
can be 1, k
can be 5).i = 1
:
nums[0]
sum = 1nums[2]
sum = 1sums
.k = 5
:
nums[4]
sum = 1nums[6]
sum = 1sums
, so return true
.The process identifies the valid split: [1], [1], [1], [1] (with indices i=1, j=3, k=5).
i
, j
, k
.j
, we do two linear scans (one for i
, one for k
).The optimization is significant: from checking all triples to a manageable double loop with fast lookups.
The key insight is to use prefix sums for fast subarray sum calculation and hash sets to efficiently check for matching subarray sums. By iterating over possible middle cuts and checking possible left and right splits, we avoid redundant checks and achieve a much faster solution than brute-force. This approach elegantly balances clarity and efficiency, making the problem solvable even for larger arrays.
class Solution:
def splitArray(self, nums):
n = len(nums)
prefix = [0] * (n + 1)
for i in range(n):
prefix[i+1] = prefix[i] + nums[i]
# Try all possible j
for j in range(3, n - 3):
sums = set()
# Left side: try all i
for i in range(1, j - 1):
sum1 = prefix[i]
sum2 = prefix[j] - prefix[i+1]
if sum1 == sum2:
sums.add(sum1)
# Right side: try all k
for k in range(j + 2, n - 1):
sum3 = prefix[k] - prefix[j+1]
sum4 = prefix[n] - prefix[k+1]
if sum3 == sum4 and sum3 in sums:
return True
return False
class Solution {
public:
bool splitArray(vector<int>& nums) {
int n = nums.size();
vector<int> prefix(n + 1, 0);
for (int i = 0; i < n; ++i)
prefix[i + 1] = prefix[i] + nums[i];
for (int j = 3; j <= n - 4; ++j) {
unordered_set<int> sums;
for (int i = 1; i <= j - 2; ++i) {
int sum1 = prefix[i];
int sum2 = prefix[j] - prefix[i + 1];
if (sum1 == sum2)
sums.insert(sum1);
}
for (int k = j + 2; k <= n - 2; ++k) {
int sum3 = prefix[k] - prefix[j + 1];
int sum4 = prefix[n] - prefix[k + 1];
if (sum3 == sum4 && sums.count(sum3))
return true;
}
}
return false;
}
};
class Solution {
public boolean splitArray(int[] nums) {
int n = nums.length;
int[] prefix = new int[n + 1];
for (int i = 0; i < n; ++i)
prefix[i + 1] = prefix[i] + nums[i];
for (int j = 3; j <= n - 4; ++j) {
HashSet<Integer> sums = new HashSet<>();
for (int i = 1; i <= j - 2; ++i) {
int sum1 = prefix[i];
int sum2 = prefix[j] - prefix[i + 1];
if (sum1 == sum2)
sums.add(sum1);
}
for (int k = j + 2; k <= n - 2; ++k) {
int sum3 = prefix[k] - prefix[j + 1];
int sum4 = prefix[n] - prefix[k + 1];
if (sum3 == sum4 && sums.contains(sum3))
return true;
}
}
return false;
}
}
var splitArray = function(nums) {
const n = nums.length;
const prefix = new Array(n + 1).fill(0);
for (let i = 0; i < n; ++i)
prefix[i + 1] = prefix[i] + nums[i];
for (let j = 3; j <= n - 4; ++j) {
const sums = new Set();
for (let i = 1; i <= j - 2; ++i) {
const sum1 = prefix[i];
const sum2 = prefix[j] - prefix[i + 1];
if (sum1 === sum2)
sums.add(sum1);
}
for (let k = j + 2; k <= n - 2; ++k) {
const sum3 = prefix[k] - prefix[j + 1];
const sum4 = prefix[n] - prefix[k + 1];
if (sum3 === sum4 && sums.has(sum3))
return true;
}
}
return false;
};