1711 - 將陣列分成三個子陣列的方案數 - 字首和 - 二分查詢
歡迎關注更多精彩
關注我,學習常用演算法與資料結構,一題多解,降維打擊。
文章目錄
題目描述
[1711] 將陣列分成三個子陣列的方案數
- https://leetcode-cn.com/problems/ways-to-split-array-into-three-subarrays/
我們稱一個分割整數陣列的方案是 好的 ,當它滿足:
陣列被分成三個 非空 連續子陣列,從左至右分別命名為 left , mid , right 。
給你一個 非負 整數陣列 nums ,請你返回 好的 分割 nums 方案數目。由於答案可能會很大,請你將結果對 109 + 7 取餘後返回。
示例 1:
輸入:nums = [1,1,1]
輸出:1
解釋:唯一一種好的分割方案是將 nums 分成 [1] [1] [1] 。
示例 2:
輸入:nums = [1,2,2,2,5,0]
輸出:3
解釋:nums 總共有 3 種好的分割方案:
[1] [2] [2,2,5,0]
[1] [2,2] [2,5,0]
[1,2] [2,2] [5,0]
示例 3:
輸入:nums = [3,2,1]
輸出:0
解釋:沒有好的分割方案。
提示:
3 <= nums.length <= 10^5
0 <= nums[i] <= 10^4
- 二分查詢
- 列舉
- 字首和
題目剖析&資訊挖掘
此題考查的是對二分演算法的應用。題目如果規模較小,也可以用二重列舉來做,複雜度O(n^2)
由於規模較大,所以需要用到二分優化。在求範圍和時,要用字首和優化。
二分查詢的特點就是查詢的答案具體有單調性, 即某一個解是非可行解,那他的某一邊都不是可行解,從而縮小範圍。
解題思路
方法一 字首和+二分查詢
分析
/*
選定一個i, j進行切割,三個子陣列表示如下:
left = nums[0,i]
mid = nums[i+1, j-1]
right = nums[j, len(num)-1]
*/
func waysToSplit(nums []int) int {
sum :=0
for j:=2; j<len(nums) ; j++ {
for i:=0; i<j-1 ; j++ {
if getSum(0,i)<=getSum(i+1, j-1) && getSum(i+1, j-1)<=getSum(j, len(nums)-1) {sum++}
}
}
return sum
}
如上思路,使用二重迴圈列舉複雜度太高。
考慮優化裡面的迴圈。
以[1, 1, 1, 3, 2, 4, 1]為例. 假設 right = [2, 4, 1], 那麼剩下的陣列是 [1, 1, 1, 3]
可行的結果有1,[1,1,3]
1,1,[1,3]
1,1,1,[3]
相當是把這個陣列再分割一下,使得left<=mid<=right 問題有多少種方法。
由於陣列和是固定的,且元素都是整數,所以會隨著i的增大left增大,同時mid會減小。
可以得出一個結論,如果left>mid, 那麼>=i的位置都是不可行解。對於mid>right也是類似。
所以對於right固定的情況下,對於剩下的陣列切割的位置是一個連續的區間。
只要求出這個區間的最左和最右的位置就可以得出答案,可以通過二分查詢解決。
思路
func waysToSplit(nums []int) int {
InitPre(nums)
sum :=0;
for j:=2; j<len(nums) ; j++ { // 列舉right, 前面至少留2個元素
lastSum := getSum(j,len(nums)-1)
// 查詢剩下陣列最左最右可行解
left, right := FindMinInd(j-1,lastSum), FindMaxInd(j-1, lastSum)
if left<0 {continue} // 沒有可行解
sum += right-left+1
sum %=MOD
}
return sum
}
注意
- 注意取模
- 剩下陣列分割時,要分成2段,每段至少有一個元素。
知識點
- 字首和
- 二分查詢
- 列舉
複雜度
- 時間複雜度:O(nlog(n))
- 空間複雜度:O(n)
參考
程式碼實現
const MOD = int(1e9)+7
var preSum []int
func InitPre (nums []int) {
preSum = make([]int, len(nums))
for i, v := range nums {
if i==0 {
preSum[i] = v
}else {
preSum[i] = preSum[i-1]+v
}
}
}
func getSum(i, j int) int{
if i==0 {
return preSum[j];
}
return preSum[j]-preSum[i-1]
}
func FindMaxInd(right, lastSum int) int {
ind :=-1 // 儲存最右邊的座標
l, r := 0, right-1
for l<=r {
mid := (l+r)>>1
if getSum(0, mid)<= getSum(mid+1, right) {
if getSum(mid+1, right)<=lastSum {// 是可行解
ind = mid//記錄最優答案
l=mid+1// 嘗試有沒有更大的座標
} else {// 說明 getSum(mid+1, right)太大了,<=mid都不是可行解
l=mid+1
}
} else {// 不滿足,說明>=mid 的分割都不成立
r=mid-1
}
}
return ind
}
func FindMinInd(right, lastSum int) int {
ind :=-1 // 儲存最左邊的座標
l, r := 0, right-1
for l<=r {
mid := (l+r)>>1
if getSum(0, mid)<= getSum(mid+1, right) {
if getSum(mid+1, right)<=lastSum { // 是可行解
ind = mid //記錄最優答案
r=mid-1 // 嘗試有沒有更小的座標
} else { // 說明 getSum(mid+1, right)太大了,<=mid都不是可行解
l=mid+1
}
} else { // 不滿足,說明>=mid 的分割都不成立
r=mid-1
}
}
return ind
}
func waysToSplit(nums []int) int {
InitPre(nums)
sum :=0;
for j:=2; j<len(nums) ; j++ {// 列舉right, 前面至少留2個元素
lastSum := getSum(j,len(nums)-1)
left, right := FindMinInd(j-1,lastSum), FindMaxInd(j-1, lastSum)
if left<0 {continue}// 沒有可行解
sum += right-left+1
//fmt.Println(j,right, left)
sum %=MOD
}
return sum
}
本人碼農,希望通過自己的分享,讓大家更容易學懂計算機知識。