算法题——截断数组

题目:截断数组


要求将数组分成三个非空子数组,并且三个子数组内元素和相等,所以该数组最少要有3个元素,另外假设数组所有元素和为x,那三个子数组的元素和都为x/3,因此数组元素和x%3为0。
使用前缀和,用数组a[]记录,数组a中每一项i都是前i项之和。可以设两个变量i和j,若数组下标从1开始,则a[i]=x/3,a[j]=x/3*2,可以直接双重循环遍历,但是时间复杂度为O(n2),由于所有测试点在1e5范围内,时间复杂度应该控制在O(nlogn)以内。

要求用两个点把数组分成三个部分,如图所示:

可以以两个分割点中的一个点为基础,去寻找相对应的另外一个点有多少个。以x/3*2的点为基点,寻找对应的x/3的点有cnt个,结果ans加上每一个x/3*2的点对应的cnt即可。
由于n最大为1e5,实际上问题是从1e5中选两个数使得数组分成三个相等的部分,最大可能满足要求的组合大概有1e5*1e5/2个,因此要开long long。
代码:

#include<iostream>
#define maxn 100000+5
typedef long long ll;
using namespace std;
int n,tmp;
int a[maxn];
int main()
{
    a[0]=0;
    cin>>n;
    for(int i=1; i<=n; ++i)
    {
        cin>>tmp;
        a[i]=a[i-1]+tmp;
    }
    if(a[n]%3)
    {
        cout<<0;
        return 0;
    }
    ll ans=0, cnt=0;
    //int x=a[n]/3;
    for(int j=2; j<n; ++j)
    {
        if(a[j-1] == a[n]/3) cnt++;
        if(a[j] == a[n]/3*2) ans+=cnt;
    }
    cout<<ans<<endl;
    return 0;
}

for循环从j=2开始遍历,先用cnt记录满足x/3的点,然后找到一个满足x/3*2的点,那么该点之前所有的满足x/3的点都可以与该点组合成满足题目要求的组合。
另外一种以x/3为基点的写法如下:

for(int i=2; i<n; ++i)
{
    if(a[i] == a[n]/3*2) cnt++;
}
for(int i=2; i<n; ++i)
{
    if(a[i] == a[n]/3*2) cnt--;
    if(a[i] == a[n]/3) ans+=cnt;
}

先找到所有的x/3*2的点,然后再遍历一次,找到x/3*2的点则cnt--,则该点之前的所有点都不能满足条件,找到x/3的点则ans加上cnt,该点与其之后的每一个x/3*2的点的组合都满足条件。

posted @ 2023-02-13 23:21  HD0117  阅读(34)  评论(0编辑  收藏  举报