1. 程式人生 > 其它 >1711 - 將陣列分成三個子陣列的方案數 - 字首和 - 二分查詢

1711 - 將陣列分成三個子陣列的方案數 - 字首和 - 二分查詢

技術標籤:leetcode二分查詢leetcode資料結構

歡迎關注更多精彩
關注我,學習常用演算法與資料結構,一題多解,降維打擊。

文章目錄

題目描述

[1711] 將陣列分成三個子陣列的方案數

  • https://leetcode-cn.com/problems/ways-to-split-array-into-three-subarrays/

我們稱一個分割整數陣列的方案是 好的 ,當它滿足:

陣列被分成三個 非空 連續子陣列,從左至右分別命名為 left , mid , right 。

left 中元素和小於等於 mid 中元素和,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

Related Topics
  • 二分查詢
  • 列舉
  • 字首和

題目剖析&資訊挖掘

此題考查的是對二分演算法的應用。題目如果規模較小,也可以用二重列舉來做,複雜度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
}


本人碼農,希望通過自己的分享,讓大家更容易學懂計算機知識。

在這裡插入圖片描述