2020.9.6字节跳动笔试题第二三题:单调栈与连续最大和

连续最大和

问题描述

简单的连续最大和就是给你一个数组,求其子序列的最大和是多少。比如[1, -1, 2, -1, 3, -2],很明显答案是 2 + (-1) + 3 = 4
例题:连续最大和

问题变形
  • HDU1003 Max Sum,不仅要求出连续最大和,还要输出这个区间下标。上述样例中,答案区间可以是[1, 5],也可以是[3, 5],具体输出看题目要求
  • 第二种变形是给你一个矩阵,要求一个子矩阵和最大(NYOJ104-最大和)
  • 第三种变形是将原数组复制m次,求拼接后的数组连续最大和(2020.9.6字节跳动笔试),n和m都是1e5级别
解决方案

原始问题解决方案很多

  • 方案一: 可以用线段树,将前缀和扔进线段树中,然后查询区间最小值,作差。但是线段树代码量稍微大了些。
  • 方案二: 基于上述思路,还可以用一个变量记录前缀和的最小值,然后用当前的前缀和减去这个值即可。
  • 方案三: 在第二种的方案的基础上改进,用一个变量记录和,如果这个变量小于0,用当前的值重新赋值,否则继续累加
  • 坑点: 看清题目要求,是否至少选一个,比如所有数都是负数,我们初始化如果为0这样就无法选择了。

以牛客例题为例
牛客网

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const double eps=1e-8;
const int INF=1e9+7;
const int MOD=1e9+7;
const int N=1e6+10;
int n,m;
int main()
{
    while(~scanf("%d",&n))
    {
        ll x;
        ll ans=-1e10,t=0;
        ll sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&x);
            /* 方案二,此题无法通过,因为必须要选一个
            sum+=x;
            t=min(t,sum);
            ans=max(ans,sum-t);
            */
            
            // 方案三
            if(t<0) t=x;
            else t+=x;
            ans=max(ans,t);
        }
        cout<<ans<<endl;
    }
    return 0;
}

字节跳动那道笔试题数据通过率90%,可能没有意识到必须要选一个,这也是一个很隐蔽的坑点。
那道题对m分情况即可,以及对前缀和sum[n]分情况即可。
其实完全不用管m的。

  • 先求出只有一个数组时的连续最大和:ans1
  • 如果sum[n]<0,拼接再多也无益,只会越加越小,这种情况就拼接一次即可,当然m需要大于等于2,拼接一次求连续最大和很简单,或者求原数组的后缀最大值和拼接一次数组的前缀最大值求和:ans2
  • 如果sum[n]>=0,那么我们只需要特殊处理首尾两个数组即可,中间的全是完整的数组对答案的贡献是(m-2)*sum[n],也要注意m是否大于等于2,首尾数组的处理同上,首数组的最大后缀加上尾部数组的最大前缀,这样就拼接起来了:ans2
  • max(ans1,ans2)就是最大值
    放一下我写的代码吧,只过了90%的数据,原题中a[i]的数据范围是[-1e6, 1e6],可能存在全为负的情况吧。
const int N=1e6+10;
ll dp[N];
ll sum[N];
int n,m;
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        sum[0]=0;
        ll ans1=0,ans2=0,ans3=0,tmp=0;
        for(int i=1; i<=n; i++)
        {
            scanf("%lld",&dp[i]);
            sum[i]=sum[i-1]+dp[i];
            tmp=min(tmp,sum[i]);
            ans1=max(ans1,sum[i]-tmp); // 中
            ans2=max(ans2,sum[i]); // 前
        }
        for(int i=n; i>=1; i--)
            ans3=max(ans3,sum[n]-sum[i-1]);  // 后
        if(sum[n]>=0)
        {
            if(m>2)
            {
                ans3+=max(0,m-2)*sum[n];
                ans3=max(ans3,ans3+ans2);
            }
            else if(m==2)
                ans3=max(ans3,ans3+ans2);
        }
        else
        {
            if(m>=2)
                ans3=max(ans3,ans3+ans2);
        }
        cout<<max(ans1,ans3)<<endl;
    }
    return 0;
}


//5 3
//1 3 -9 2 4

单调栈应用

什么是单调栈,就是用栈来维护一个单调的序列
序列用数组存起来,将位置扔进栈中,每次比较当前元素与栈顶位置的元素。

典型应用,求两侧第一个大于等于或者小于等于当前数的值或位置。

例题一:POJ2559 Largest Rectangle in a Histogram
求最大的木板面积

POJ
这个题我们求出每个值两侧比当前值小的位置即可。

typedef long long ll;
const int N=1e5+10;
int n,a[N];
ll l[N],r[N];
int main()
{
    while(~scanf("%d",&n)&&n)
    {
        memset(l,0,sizeof(l));
        memset(r,0,sizeof(r));
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        stack<int>q;
        int i=2;
        q.push(1);
        while(i<=n)
        {
            if(!q.empty()&&a[i]<a[q.top()])
            {
                l[q.top()]=i-1;
                q.pop();
            }
            else
                q.push(i++);
        }
        while(!q.empty())
        {
            l[q.top()]=i-1;
            q.pop();
        }
        i=n-1;
        q.push(n);
        while(i>=1)
        {
            if(!q.empty()&&a[i]<a[q.top()])
            {
                r[q.top()]=i+1;
                q.pop();
            }
            else
                q.push(i--);
        }
        while(!q.empty())
        {
            r[q.top()]=i+1;
            q.pop();
        }
//        for(int i=1;i<=n;i++)
//            printf("%lld%c",l[i],i==n?'\n':' ');
//        for(int i=1;i<=n;i++)
//            printf("%lld%c",r[i],i==n?'\n':' ');
        ll ans=0;
        for(int i=1; i<=n; i++)
            ans=max(ans,(l[i]-r[i]+1)*a[i]);
        cout<<ans<<endl;

    }
    return 0;
}

例题二:超级码力在线编程大赛初赛 第1场 - 3.大楼间穿梭
大楼穿梭
如题,每次跳跃可以跳到右边第一个大于等于当前数的位置上。
问题转化为如何求出右侧第一个大于等于当前数的位置。用单调栈维护即可,从左往右扫描数组,将位置扔进栈中,栈中的元素表示需要确定右侧位置的元素,如果当前元素比栈顶元素大或等,那么栈顶元素的右侧要找的第一个位置就是当前位置,重复判断直到栈为空;将当前位置也扔进栈中。

class Solution {
public:
    /**
     * @param heights: the heights of buildings.
     * @param k: the vision.
     * @param x: the energy to spend of the first action.
     * @param y: the energy to spend of the second action.
     * @return: the minimal energy to spend.
     */
     typedef long long ll;
     const static ll INF=1e15+7;
     const static int N=1e6+10;
     int vis[N],head[N],tot;
    ll d[N];
    struct node
    {
        int v;
        ll cost;
        friend bool operator<(node A,node B)
        {
            return A.cost>B.cost;
        }
    };
    struct Edge
    {
        int to,next;
        ll cost;
    } e[N*2];
    priority_queue<node>q;
    void init()
    {
        tot=0;
        memset(head,-1,sizeof(head));
        memset(vis,0,sizeof(vis));
    }
    void add(int u,int v,int w)
    {
        e[tot].to=v,e[tot].cost=w,e[tot].next=head[u];
        head[u]=tot++;
    }
    
    void add_jump(vector<int> &a,int n,int x,int k)
    {
        stack<int>tmp;
        tmp.push(0);
        int i=1;
        while(i<n)
        {
            if(!tmp.empty()&&a[i]>=a[tmp.top()])
            {
    //                res[tmp.top()]=i;
                if(i-tmp.top()<=k)
                    add(tmp.top(),i,x);
                tmp.pop();
            }
            else
                tmp.push(i++);
        }
    }
    
    ll DIJ(int s,int t)
    {
        while(!q.empty()) q.pop();
        for(int i=s;i<=t;i++)
            d[i]=i==s?0:INF;
        q.push(node{s,d[s]});
        while(!q.empty())
        {
            node TC=q.top();
            q.pop();
            int u=TC.v;
            if(vis[u]) continue;
            vis[u]=1;
            for(int i=head[u];i+1;i=e[i].next)
            {
                int v=e[i].to;
                if(d[v]>d[u]+e[i].cost)
                {
                    d[v]=d[u]+e[i].cost;
                    q.push(node{v,d[v]});
                }
    
            }
        }
        //cout<<d[t]<<endl;
        return d[t];
    }
    long long shuttleInBuildings(vector<int> &heights, int k, int x, int y) {
        // write your code here.
       init();
       int n=heights.size();
       for(int i=0; i<n; i++)
        {
            if(i>0)
                add(i-1,i,y);
            if(i>1)
                add(i-2,i,y);
        }
       add_jump(heights,n,x,k);
        return DIJ(0,n-1);
    }
};

字节跳动那道题求每个数两边第一个大于等于当前数的位置,然后进行乘积,求最大值。比如:
5
5 4 3 4 5

输出 8
说明
很简单顺序倒序扫用单调栈找位置即可,不知道哪里错了,数据只通过50%左右。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const double eps=1e-8;
const int INF=1e9+7;
const int MOD=1e9+7;
const int N=1e6+10;
int a[N];
ll b[N],c[N];
int n;
void Get()
{
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c));
    stack<int>tmp;
    tmp.push(1);
    int i=2;
    while(i<=n)
    {
        if(!tmp.empty()&&a[i]>a[tmp.top()])
        {
            b[tmp.top()]=i;
            if(i-tmp.top()<=k)
                add(tmp.top(),i,x);
            tmp.pop();
        }
        else
        {
            tmp.push(i);
            i++;
        }
    }
    while(!tmp.empty()) tmp.pop();
    tmp.push(n);
    i=n-1;
    while(i>=1)
    {
        if(!tmp.empty()&&a[i]>a[tmp.top()])
        {
            c[tmp.top()]=i;
            tmp.pop();
        }
        else
        {
            tmp.push(i);
            i--;
        }
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        Get();
        ll ans=0;
//        for(int i=1;i<=n;i++)
//            printf("%lld ",b[i]);
//        puts("");
//        for(int i=1;i<=n;i++)
//            printf("%lld ",c[i]);
//        puts("");
        for(int i=1;i<=n;i++)
            ans=max(ans,c[i]*b[i]);
        cout<<ans<<endl;
    }
    return 0;
}

/*

5
5 4 3 4 5

*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值