将数组分成三个子数组的方案数
难度:
标签:
题目描述
A split of an integer array is good if:
- The array is split into three non-empty contiguous subarrays - named
left
,mid
,right
respectively from left to right. - The sum of the elements in
left
is less than or equal to the sum of the elements inmid
, and the sum of the elements inmid
is less than or equal to the sum of the elements inright
.
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
代码结果
运行时间: 124 ms, 内存: 27.2 MB
/*
* Problem description:
* We call a split of an integer array good if:
* - The array is split into three non-empty continuous subarrays named left, mid, and right from left to right.
* - 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.
* Given a non-negative integer array nums, return the number of good ways to split nums. Since the answer may be large, return it modulo 10^9 + 7.
*/
import java.util.*;
import java.util.stream.*;
public class GoodSplitsStream {
public int waysToSplit(int[] nums) {
int MOD = 1_000_000_007;
int n = nums.length;
int[] prefixSum = new int[n + 1];
for (int i = 0; i < n; i++) {
prefixSum[i + 1] = prefixSum[i] + nums[i];
}
return IntStream.range(1, n).parallel().map(i -> {
int leftSum = prefixSum[i];
int left = binarySearch(prefixSum, i + 1, n, leftSum * 2);
int right = binarySearch(prefixSum, i + 1, n, leftSum + (prefixSum[n] - leftSum) / 2 + 1);
return right - left;
}).reduce(0, (a, b) -> (a + b) % MOD);
}
private int binarySearch(int[] prefixSum, int low, int high, int target) {
while (low < high) {
int mid = (low + high) / 2;
if (prefixSum[mid] < target) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
}
解释
方法:
本题解采用前缀和和双指针策略来解决问题。首先计算数组的总和total,然后遍历数组以确定合适的right分界点。对于每一个right,使用两个指针left_min和left_max来确定可能的左区间的范围。left_min是使得left区间的和最小但仍满足left区间的和小于等于mid区间的和的位置,left_max则是left区间和可以达到的最大位置,同时满足left区间的和小于等于mid区间的和的条件。如果left_min到left_max之间有合法的位置,那么这些位置都可以作为合法的分割方案。最后,所有满足条件的分割方法的数量需要对109+7取模。
时间复杂度:
O(n)
空间复杂度:
O(1)
代码细节讲解
🦆
在确定right分界点的循环中,为什么选择`if 3 * sum_right > 2 * total`作为终止条件?这个条件是如何保证不遗漏任何可能的分割方案的?
▷🦆
在调整left_min和left_max时,你是如何保证在每一步迭代中都满足left区间和小于等于mid区间和的条件的?
▷🦆
为什么在进行left_max的调整时使用的条件是`2 * sum_left_max <= sum_right`,而不是直接比较sum_left_max和sum_right的大小?
▷