1712 将数组分成三个子数组的方案数(前缀和 + 二分查找)

41 篇文章 0 订阅
18 篇文章 0 订阅

1. 问题描述:

我们称一个分割整数数组的方案是好的 ,当它满足:数组被分成三个非空连续子数组,从左至右分别命名为 left ,mid ,right 。left 中元素和小于等于 mid 中元素和,mid 中元素和小于等于 right 中元素和。给你一个非负整数数组 nums ,请你返回好的分割nums 方案数目。由于答案可能会很大,请你将结果对 10 ^ 9 + 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ways-to-split-array-into-three-subarrays

2. 思路分析:

① 由数据规模可以知道这道题目应该使用O(nlogn)或者是O(n)的算法进行求解,所以最多可以遍历一遍数组的操作。我们的任务是需要将数组分割成三个区间,使得这个三个区间的和依次是非递减的,由于要计算出每个区间的和所以一开始想到的是前缀和,但是具体没有想到如何进行分割(可能是太久没有做题脑子都蜕化了),于是看了一下力扣的题解发现可以使用二分查找的思路进行求解。我们可以遍历nums数组,以当前遍历的i位置为第一个区间的终点,固定好了这个区间之后那么比较容易想到的是要求解出第二个区间的起始位置与结束位置,起始位置与结束位置的区间长度就是以当前的i位置为第一个区间能够划分的最大区间的个数,对于其他的起始位置也是类似的所以在for循环中进行求解即可

② 所以问题就转换为了如何求解出第二个区间的起始位置与结束位置,这里可以使用二分查找的方法(比较难想到的一点)来查找第一个大于等于第一个区间的和的位置,所以之前的前缀和数组现在就发挥了作用,我们可以在前缀和数组中找到第一个大于等于当前[0:i]区间的位置j(j > i),所以可以得到pre[j] - pre[i] >= pre[i],也即pre[j] >= 2 * pre[i],所以我们可以在前缀和数组pre中查找第一个大于等于2 * pre[i]的位置即可,因为使用的是Python语言所以可以使用bisect模块中的biserct_left方法进行查找(或者自己写二分查找的方法),这个j位置就是第二个区间的起始位置,但是第二个区间什么时候结束呢?假设第二个区间的结束位置为k,则由区间之间的关系可以得到关系式:pre[n] - pre[k] >= pre[k] - pre[i],也即pre[k] <= (pre[i] + pre[n]) // 2,我们可以使用python中的bisect_right方法找出第一个大于(pre[i] + pre[n]) // 2的位置k,那么可以知道j~k - 1的位置就是第二个区间的起始位置与结束位置,那么k - j的差值就是以i作为第一个区间的结束位置的能够划分的最大区间数目,对于数组中的每一个为主都可以这样固定这样一个位置那么最终就可以求解出方案的数目

bisect_left:查找第一个大于等于目标值target的位置
bisect_right:查找第一个大于目标值target的位置

③ 并且根据区间的和之间的关系可以得出:v[i]<=v[x]<=v[y]<=(v[n]+v[i])/2,所以当3 * v[i] > v[n]之后就可以break了,所以这里可以做一个优化,感觉最主要的是要想到二分查找,for循环固定第一个区间的位置,然后使用二分查找前缀和数组中的起始位置与结束位置即可计算出区间的数目

3. 代码如下:

import bisect
import itertools
from typing import List


class Solution:
    def waysToSplit(self, nums: List[int]) -> int:
        mod = 10 ** 9 + 7
        # 使用accumulate函数计算前缀和
        pre = list(itertools.accumulate(nums))
        res, n = 0, len(nums) - 1
        for i in range(len(nums)):
            if 3 * pre[i] > pre[n]: break
            # 计算第二个区间的起始位置
            l = max(i + 1, bisect.bisect_left(pre, pre[i] + pre[i]))
            # 计算第二个区间的结束位置
            r = min(n, bisect.bisect_right(pre, (pre[i] + pre[n]) // 2))
            # l~r-1就是第二个区间可以划分的位置
            res = (res + max(0, r - l)) % mod
        return res

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值