A split of an integer array is good if:
left
, mid
, right
respectively from left to right.left
is less than or equal to the sum of the elements in mid
, and the sum of the elements in mid
is less than or equal to the sum of the elements in right
.Given nums
, an array of non-negative integers, return the number of good ways to split nums
. As the number may be too large, return it modulo 109 + 7
.
Example 1:
Input: nums = [1,1,1] Output: 1 Explanation: The only good way to split nums is [1] [1] [1].
Example 2:
Input: nums = [1,2,2,2,5,0] Output: 3 Explanation: There are three good ways of splitting nums: [1] [2] [2,2,5,0] [1] [2,2] [2,5,0] [1,2] [2,2] [5,0]
Example 3:
Input: nums = [3,2,1] Output: 0 Explanation: There is no good way to split nums.
Constraints:
3 <= nums.length <= 105
0 <= nums[i] <= 104
This problem asks us to find the number of ways to split an array into three non-empty contiguous subarrays (left
, mid
, right
) such that the sum of elements in left
is less than or equal to the sum of elements in mid
, and the sum of elements in mid
is less than or equal to the sum of elements in right
.
The most efficient approach leverages prefix sums and binary search.
1. Prefix Sum:
We first calculate the prefix sum array s
. s[i]
represents the sum of elements from nums[0]
to nums[i]
. This allows us to efficiently calculate the sum of any subarray in O(1) time. For example, the sum of the subarray from index i
to j
(inclusive) is s[j] - s[i-1]
(handling i=0
appropriately).
2. Iterative Approach with Binary Search:
The core idea is to iterate through all possible positions for the split between left
and mid
. For each such split (i
):
Left Sum: The sum of left
is s[i]
.
Finding mid
and right
: We need to find the valid range of indices (j
, k
) where j
is the start of mid
and k
is the start of right
.
Condition 1: sum(mid) >= sum(left)
: This implies s[j] - s[i] >= s[i]
. We can efficiently find the smallest j
satisfying this condition using binary search on the prefix sum array.
Condition 2: sum(right) >= sum(mid)
: This implies s[n-1] - s[k-1] >= s[k-1] - s[i]
, which simplifies to s[k-1] <= (s[n-1] + s[i]) / 2
. Again, binary search helps to quickly find the largest k
fulfilling this.
Counting Valid Splits: The number of valid splits for the current i
is k - j
. We accumulate this count in ans
.
3. Time Complexity Analysis:
i
): O(n)j
and k
: O(log n) for each i
Therefore, the overall time complexity is O(n log n). The space complexity is O(n) due to the prefix sum array.
4. Code Implementation (Python):
import bisect
from itertools import accumulate
class Solution:
def waysToSplit(self, nums: List[int]) -> int:
mod = 10**9 + 7
s = list(accumulate(nums))
ans, n = 0, len(nums)
for i in range(n - 2):
j = bisect_left(s, s[i] << 1, i + 1, n - 1) # Binary search for j
k = bisect_right(s, (s[-1] + s[i]) >> 1, j, n - 1) # Binary search for k
ans = (ans + k - j) % mod
return ans
The other language implementations (Java, C++, Go, Javascript) follow the same algorithmic approach, differing only in syntax and library functions for binary search and prefix sum calculations. The core logic remains consistent.