bilibili 哔哩哔哩面试题 大鱼吃小鱼 单调栈

题目描述:

给定 n n n条鱼及其各自的大小 w e i g h t [ i ] weight[i] weight[i],会无限重复以下过程,直到鱼的数量稳定:
左边的大鱼会吃掉其右边第一条比其小的鱼;
需要注意的是,在一次吃鱼的过程中,可能有多条大鱼吃同一条小鱼。问上述过程进行多少次后鱼的数量不再发生改变。
例如有四条鱼,从左到右各自的大小依次是: 4 , 4 , 3 , 3 4, 4, 3, 3 4,4,3,3
那么在第一次吃鱼时两条大小为 4 4 4的鱼会同时吃第一条大小为 3 3 3的鱼,此时剩余的鱼从左到右各自的大小为: 4 , 4 , 3 4, 4, 3 4,4,3
在第二次吃鱼时,两条大小为 4 4 4的鱼会同时吃掉剩下的大小为 3 3 3的鱼,此时剩余的鱼从左到右各自的大小为: 4 , 4 4, 4 4,4
之后鱼的数量将不再发生改变,所以需要输出 2 2 2,表示两次吃鱼之后鱼类数量不再改变。

题解:

我们先来考虑一个简单一点的问题:如何求最终会剩下几条鱼。由于每条鱼只会被其左边的某条鱼吃掉,我们可以逆序的处理每条鱼。具体地,在逆序处理的过程中只要有一条鱼比前面访问的鱼大,那么前面访问的鱼一定会被吃掉,实际上我们可以推出前面访问的所有比当前鱼小的均会被吃掉,这是因为:如果当前的鱼最终不被吃掉那么当前的鱼在进行足够多的轮数之后,一定会吃掉右边比其小的鱼;如果当前的鱼被其左边的某条鱼吃掉,那么左边吃它的鱼一定比当前的鱼大,那么由不等式的传递性可以知道,左边的这条鱼也比所有比右边当前鱼小的鱼大,那么右边的鱼会被这条更大的鱼吃掉。这样的话最终剩下的鱼的大小一定是单调不下降的(从左到右。为了方便,后文用单调减少代替单调不上升,单调增加代替单调不下降),这就符合单调栈的特点。所以我们如果要求最终还剩几条鱼我们可以用一个单调栈,然后逆序的访问,保证栈中的元素单调减少,这样最终栈的大小就是剩余鱼的个数。
有了前面的铺垫我们再来考虑如何求鱼的个数稳定所需要的轮数。实际上我们依然使用单调栈,只需要多维护一个状态,表示只考虑当前位置和其后面的鱼组成的序列稳定所经过的轮数。
那么这个状态如何更新呢?访问到一条鱼的时候,我们只需要计算出单调栈中有多少条鱼比自己轻,我们记为 c n t cnt cnt,那么这些鱼都是会被吃掉的,也就是至少需要 c n t cnt cnt轮才会稳定。但是这样考虑是不周全地,例如序列: 5 , 4 , 1 , 2 , 3 5, 4, 1, 2, 3 5,4,1,2,3,到 5 5 5的时候此时单调栈里面只有 4 4 4这一个元素,也就是至少需要 1 1 1轮,但很明显上述鱼个数稳定需要 3 3 3轮,这时我们还需要考虑单调栈里面元素稳定需要花费的轮数,当访问到 4 4 4的时候实际上可以得出 4 , 1 , 2 , 3 4, 1, 2, 3 4,1,2,3需要三轮才能稳定,而到 5 5 5的时候则应该取 m a x ( 1 , 3 ) max(1, 3) max(1,3)表示最终的答案,这是因为 5 5 5 4 4 4 4 4 4吃比 4 4 4小的鱼可以同时进行,因此应该取最大值表示最终所需要的轮数。
实际上我们发现我们的过程中在对于 5 , 4 , 1 , 2 , 3 5, 4, 1, 2, 3 5,4,1,2,3的情况我们用 4 4 4把后面三条鱼吃掉了,但实际上是 4 4 4在第一轮就会被 5 5 5吃掉这是不是会出现问题呢?实际上这并不影响答案的正确性,正如前面所提到的, 1 , 2 , 3 1, 2, 3 1,2,3终究会被吃掉,不论是被 4 4 4还是被 5 5 5吃掉,我们让 4 4 4来做该操作,是让 4 4 4代替 5 5 5来吃鱼,后续的花费会在取最大值的过程中转换。

代码:

#include <bits/stdc++.h>

const int MAXN = 1e5 + 10;

using namespace std;

int n;
int weight[MAXN];

// first 表示鱼的大小,second表示轮数
stack<pair<int, int>> s;

int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 0; i < n; i++) { cin >> weight[i]; }
    for (int i = n - 1; i >= 0; i--) {
        int turn = 0;
        while(!s.empty() && s.top().first < weight[i]) {
            turn++;
            turn = max(turn, s.top().second);
            s.pop();
        }
        s.push({weight[i], turn});
    }
    cout << s.top().second << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值