1712 Ways to Split Array Into Three Subarrays
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
嘛,這題,跟著提示大体想出了做法。但特么的二分分了两个星期还没分对。其实难点是,我们二分找的范围和找到的是什么。一开始,没想清楚二分的条件,以为是leftSum <= midSum <= rightSum。所以很多corner case逃不掉,以为如果不能切的地方我们也要同时判断,所以很麻烦。最后看了答案,发现只要简单数学转换一下,就知道,我们要找的是min i >= leftSum和max i <= remain/2。然后把range规定好就能套九章模板了。
这里,思路是这样的。基本上我们要在数组里切两刀。先把问题缩小,我们定好左边一刀切哪里。然后找右边那一刀的可切范围。譬如,[1, 2, 2, 2, 5, 0],如果我们第一刀且在1后面,那么,我们第二刀的范围是第一个2后面,或者第 二个2后面。这里我们就有2个解。因为是正数,所以我们在搜范围的时候能运用二分+prefixSum
首先,我们一个一个loop第一刀。因为每一节都要有数字,所以最后loop到n - 2。因为我们起码要三段等于,所以如果切完第一刀,发现后面不能切成两段,那么我们就知道这个误解,break掉。然后,二分找第二刀的min和max。算个和就ok了。T:O(nlogn) S:O(n)
public int waysToSplit(int[] nums) {
if (nums == null || nums.length < 3) {
return 0;
}
int MOD = 1000000007;
int n = nums.length;
// calculate prefixSum, so we can get range sum easily
int[] prefixSum = new int[n + 1];
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
}
long total = 0;
for (int i = 0; i < n - 2; i++) {
int leftSum = prefixSum[i + 1];
int remain = prefixSum[n] - leftSum;
if (remain < leftSum * 2) {
break;
}
int first = findLeft(i, n, leftSum, prefixSum);
int last = findLast(i, n, remain / 2, prefixSum);
total += Math.max(last - first + 1, 0);
}
return (int) (total % MOD);
}
// find fist i that is >= leftSum
private int findLeft(int i, int n, int target, int[] prefixSum) {
int left = i + 1;
int right = n - 2;
while (left + 1 < right) {
int mid = left + (right - left) / 2;
int cur = prefixSum[mid + 1] - prefixSum[i + 1];
if (cur >= target) {
right = mid;
} else {
left = mid;
}
}
if (prefixSum[left + 1] - prefixSum[i + 1] >= target) {
return left;
} else {
return right;
}
}
// find last i that is <= remain/2
private int findLast(int i, int n, int target, int[] prefixSum) {
int left = i + 1;
int right = n - 2;
while (left + 1 < right) {
int mid = left + (right - left) / 2;
int cur = prefixSum[mid + 1] - prefixSum[i + 1];
if (cur <= target) {
left = mid;
} else {
right = mid;
}
}
if (prefixSum[right + 1] - prefixSum[i + 1] <= target) {
return right;
} else {
return left;
}
}
Last updated
Was this helpful?