【2021】腾讯秋招技术岗编程 有效序列的数量

 

如果序列中数据都不重复,那么这个题目难度会小很多。本文介绍三种解法。

(1)暴力枚举法

暴力枚举子序列左右端点i和j,检查a[i+1]到a[j-1]间是否有比端点更小的值,复杂度O(N^3)。可得20分。

(2)RMQ优化

RMQ(Range Minimum/Maximum Query)可以看成是一种查找结构,能快速找到区间内极值,每次查找的复杂度是O(1)级别。这样可以把上述算法“检查a[i+1]到a[j-1]间是否有比端点更小的值”优化,整个算法复杂度O(N^2),可得70分。

介绍一种骗分技巧,出题人在出大数据的时候,例如上面100000级别数据,经常会出所有数据全部相同的数据点,或者是1,2,3,4.....100000这样的数据点。如果最后实在没有好方法,且时间充足,可编写代码针对这些特殊情况做处理。此方法不保证有效性!!!

(3)单调栈

经验之谈:如果枚举a[i]为序列次小值,找到数据a[i]左右两端第一个比他小得的问题,这是典型的单调栈问题。读者必须掌握单调栈基础用法才可以阅读下面解题思路。

此题目可以考虑枚举第i个元素,a[i]向左找到第一个比a[i]小的值a[k],此时[k,i]区间一定满足条件,

那么是否能继续向左找更小呢?模拟后发现不可以,例如序列1 3 6 5 ,5向左找到3就可以停下来了,以5为次小的序列是(3,6, 5),(1,3,6,5)是不行的。当然也有可能i元素左侧没有比它小的,这样就找不到满足条件子序列。

问题转换为对于a[i],左右两侧是否存在比它小的值。

如果序列中数据都不重复,按上述分析用单调栈双向各做一次,枚举i元素为次小时所有情况即可得到结果。如果数据有重复呢?

对于重复数据,比如1,3,1,4,1,第5个1为次小元素,左边能和它搭配有两个1,而单调栈处理时碰见满足条件的就会停止出栈操作。这样的话需要调整下栈结构,使其不要存储重复元素,但又能知道重复元素有多少个。方法很多,例如入栈元素用结构体,或者用计数器(map)记录栈中相同值元素个数(代码用map)。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int n,a[100005];
ll ans=0;
int main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    int i,j;
    cin>>n;
    for(i=1; i<=n; i++)
        cin>>a[i];
    stack<int>st;
    map<int,int>mp;/**< mp计数器,用于记录栈中相同元素的个数,确保栈中元素一定递增,不会有相同值 */
    for(i=1; i<=n; i++)
    {
        while(st.size()&&a[st.top()]>a[i])
        {
            mp[a[st.top()]]=0;/**< 出栈计数器清零 */
            st.pop();
        }
        if(st.empty())
            st.push(i);
        else
        {
            if(a[i]>a[st.top()]) /**< 这个是计算a[i]为次小值,左边比a[i]大的都已经出栈了,栈顶比a[i]小 */
                ans++,st.push(i);
            else if(a[i]==a[st.top()])
            {/**< 处理栈顶和a[i]相同的情况,比如例如这种情况3 3 3 3,第4个3和左边的几个3都能搭配 */
                ans+=mp[a[st.top()]];/**< 注意这个时候不要进栈,后面会把栈顶值mp[a[i]]++ */
                if(st.size()>1)/**< 1 3 3 3 3,这时第4个3不但和左边3能搭配,还能和1搭配,判断下栈里还有没有比a[i]小的 */
                    ans++;
            }
        }
        mp[a[i]]++;
    }
    while(st.size())
        st.pop();
    for(i=n; i>=1; i--)/**< 反向就无需处理重复元素了 */
    {
        while(st.size()&&a[st.top()]>=a[i])
            st.pop();
        if(!st.empty())
            ans++;
        st.push(i);
    }
    cout<<ans;
    return 0;
}

作者在现场写码时只正向一次单调栈就解决了。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int n,a[100005];
ll ans=0;
int main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    int i,j;
    cin>>n;
    for(i=1; i<=n; i++)
        cin>>a[i];
    stack<int>st;
    map<int,ll>mp;/**< mp计数器,用于记录栈中相同元素的个数,确保栈中元素一定递增,不会有相同值 */
    for(i=1; i<=n; i++)
    {
        while(st.size()&&a[st.top()]>a[i])
        {/**< 下面这句话实际上是计算a[i]为最小值情况,因为凡是出栈元素,其值均大于a[i]
            为什么用mp,比如序列 3 3 3 3 1,此时栈里面只有一个3,但是mp[3]=4,而1和左边的4个3都能构成序列 */
            ans+=mp[a[st.top()]];
            mp[a[st.top()]]=0;/**< 出栈计数器清零 */
            st.pop();
        }
        if(st.empty())
            st.push(i);
        else
        {
            if(a[i]>a[st.top()]) /**< 这个是计算a[i]为次小值,左边比a[i]大的都已经出栈了,栈顶比a[i]小 */
                ans++,st.push(i);
            else if(a[i]==a[st.top()])
            {  /**< 处理栈顶和a[i]相同的情况,比如例如这种情况3 3 3 3,第4个3和左边的几个3都能搭配 */
                ans+=mp[a[st.top()]];/**< 注意这个时候不要进栈,后面会把栈顶值mp[a[i]]++ */
                if(st.size()>1)/**< 1 3 3 3 3,这时第4个3不但和左边3能搭配,还能和1搭配,判断下栈里还有没有比a[i]小的 */
                    ans++;
            }
        }
        mp[a[i]]++;
    }
    cout<<ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值