牛客网 字节跳动2018校招测试开发方向 算法第二题

给定一个数组,要求找出一个区间,使区间内最小数乘以区间内所有数之和的结果最大。题目要求计算所有可能的区间并返回最大值。可以通过单调递增栈优化搜索过程,降低时间复杂度。本文探讨了解题思路和算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

给定一个数组序列, 需要求选出一个区间, 使得该区间是所有区间中经过如下计算的值最大的一个:

区间中的最小数 * 区间所有数的和最后程序输出经过计算后的最大值即可,不需要输出具体的区间。如给定序列  [6 2 1]则根据上述公式, 可得到所有可以选定各个区间的计算值:

[6] = 6 * 6 = 36;

[2] = 2 * 2 = 4;

[1] = 1 * 1 = 1;

[6,2] = 2 * 8 = 16;

[2,1] = 1 * 3 = 3;

[6, 2, 1] = 1 * 9 = 9;

从上述计算可见选定区间 [6] ,计算值为 36, 则程序输出为 36。

区间内的所有数字都在[0, 100]的范围内;

输入描述:

第一行输入数组序列长度n,第二行输入数组序列。
对于 50%的数据, 1 <= n <= 10000;
对于 100%的数据, 1 <= n <= 500000;

输出描述:

输出数组经过计算后的最大值。

输入例子:

8
1 1 4 6 9 8 5 2

输出例子:

140

 

思路:

求子区间中的最小数 * 子区间所有数的和 计算区间中最大值。

暴力想法:假设一个数是某个区间的最小数,向左右两边一直延申,找满足条件的最长的区间。最坏结果是每个数全找,logN^2

由暴力想法衍生出来的,可减少时间的操作是记录,记录以前算过的而这次不用算了的。记录需要额外空间。

以空间换时间是很常用的手法。

如给出样例,一数字的后续区间若在都是比该数字大的则可在遇见“小的数”的时候才开始计算,因为前面算的都是越来越大。更关键的是把“已计算的合法区间信息”给与”小的数“,这才能减少时间。

 

单调递增栈,比存在小

单调栈的灵感可能是来源于区间以及单调性(比该数字大,都大)。单调栈,入栈只为记录,出栈在开始真正操作。

一个单调递增栈,栈顶无需考虑下面的栈(都比它小),而下面的栈会享有栈顶带来的额外的区间信息(一定都它大)。

入栈元素若会破坏单调性,说明栈顶(以及其他比入栈元素大)要出栈更新了。

#include <cstdio>
#include <stack>
#include <algorithm>
using namespace std;

const int _max = 50007;
struct Poi
{
    int num;        //记录该数字
    int region;     //记录该数字可用区间(区间内不含本身数字)
}p[_max];
stack<Poi> incrementStack;

int main()
{
    int T;
    scanf("%d",&T);
    for( int i=0; i<T; i++ )
    {
        scanf("%d",&p[i].num);
        p[i].region = 0;
    }
    p[T].num = p[T].region = 0;  //为了最后把栈弹空


    int ans = -1;
    for( int i=0; i<T+1; i++ )
    {
        //对于会破坏栈单调性的数据,要不断出栈保证栈的单调性
        while( !incrementStack.empty() && incrementStack.top().num >=p[i].num ) 
        {
            Poi outStackp = incrementStack.top();
            incrementStack.pop();

            p[i].region = outStackp.num+outStackp.region+p[i].region;  //更新p[i].region
            ans = max( ans,  outStackp.num * (outStackp.num+outStackp.region+p[i].region)  );  //更新ans
        }
        incrementStack.push(p[i]);
    }

    printf("%d\n",ans);
    return 0;
}

 

普通的数组记录,比任意小

时间上和单调栈差不多,空间大在了第三个数组。

与单调栈不同,把精力发在区间右测外最小的元素上,一旦找到,整个区间的信息都给它就好了。

#include <cstdio>
#include <algorithm>
using namespace std;
const int _max = 500008; 
int num[_max];
int region[_max] ;   //该节点合法区间
int region_L[_max];  //最小左节点,记录为避免重复计算

int main()
{
    //freopen("input.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for( int i=0;i<T;i++ )
    {
        scanf("%d",&num[i]);
        region[i] = num[i];
        region_L[i] = i;
    }

    int findSamllerNum = 0;
    int ans = -1;
    for( int i=0; i<T; i++ )
    {
        findSamllerNum = i;
        
        while( findSamllerNum<T && num[findSamllerNum+1]>num[i] )  //> 比 >= 好,避免极端数据11111....
        {
            ++findSamllerNum;
            region[i] += num[findSamllerNum];
        }
        
        findSamllerNum++;
        if( region_L[findSamllerNum] > i )  //这里加if避免重复记录
        {
            region_L[findSamllerNum] = i;
            region[findSamllerNum] += region[i] ;  //更新SamllerNum region
        }

        ans = max( ans ,  region[i] *num[i] ); //更新ans
    }
    printf("%d\n",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值