1711 - 将数组分成三个子数组的方案数 - 前缀和 - 二分查找

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

题目描述

[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
}


本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组是一种用于加速前缀和操作的数据结构。它可以在O(logn)的时间复杂度内更新单个元素,并且可以在O(logn)的时间复杂度内查询一个区间的和。\[1\] 树状数组的基本思想是将数组解成若干个长度为2的幂次的区间,每个区间的和都可以通过一系列区间和的累加得到。树状数组的每个节点都存储了一段区间的和,通过不断迭代lowbit()运算,可以得到从1到x之间的和。\[3\] 在树状数组的实现中,可以使用add()函来更新单个元素的值,使用query()函来查询一个区间的和。add()函通过迭代lowbit()运算,将更新的值加到对应的节点上。query()函通过计算两个前缀和的差值来得到一个区间的和。\[2\] 差树状数组是树状数组的一种变体,它可以用来求解区间最大值。差树状数组的基本思想是将原始数组转化为差数组,然后对差数组建立树状数组。通过查询树状数组得到的前缀和,再加上差数组前缀和,就可以得到原始数组的区间最大值。\[2\] 综上所述,树状数组是一种用于加速前缀和操作的数据结构,可以在O(logn)的时间复杂度内更新单个元素和查询一个区间的和。差树状数组是树状数组的一种变体,用于求解区间最大值。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* *3* [RMQ问题--------树状数组](https://blog.csdn.net/weixin_43743711/article/details/107191842)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值