大厂秋招真题【单调栈】Bilibili2021秋招-大鱼吃小鱼

20 篇文章 0 订阅
12 篇文章 0 订阅

题目描述与示例

题目描述

小明最近喜欢上了俄罗斯套娃、大鱼吃小鱼这些大的包住小的类型的游戏。

于是小明爸爸给小明做了一个特别版的大鱼吃小鱼游戏,他希望通过这个游戏能够近一步提高小明的智商。

游戏规则如下:

现在有N条鱼,每条鱼的体积为Ai,从左到右排成一排。A数组是一个排列。

小明每轮可以执行一次大鱼吃小鱼的操作。一次大鱼吃小鱼的操作:对于每条鱼,它在每一次操作时会吃掉右边比自己小的第一条鱼。

值得注意的是,在一次操作中,每条鱼吃比自己小的鱼的时候是同时发生的。

举一个例子,假设现在有三条鱼,体积为分别[5,4,3]5443,一次操作后就剩下[5]一条鱼。

爸爸问小明,你知道要多少次操作,鱼的数量就不会变了嘛?

输入描述

第一行输入长度N

第二行输入A数组,数字之间用空格隔开

1<=N<=10^5`,`1<=Ai<=N

输出描述

一个正整数, 表示要多少次操作,鱼的数量就不会变了。

示例一

输入

3
1 2 3

输出

0

说明

无需操作A数组。

示例二

输入

6
4 3 2 3 2 1

输出

2

说明

[4,3,2,3,2,1]-->[4,3]-->[4]

解题思路

用比较严谨的数学语言来翻译该题,描述如下。

对于数组nums中所有尽可能长的严格递减子区间[a, b],每一次我们都用区间的最大值a来替换掉该区间,得到一个新的数组nums_new。对于nums_new做相同的操作,直到nums_new不再发生变化,问一共需要几次操作。

该问题显然可以用模拟的暴力方法来解决,时间复杂度为O(N^2),部分用例将无法通过。在想不到更优解法的时候,可以尝试暴力法。本篇题解主要讨论单调栈解法。

考虑如下用例5 4 4 2 2 1 3 2,会经历以下过程。

在这里插入图片描述

观察可以发现,由于位于右边的较小的鱼迟早会被位于左边的较大的鱼吃掉,假设位于左边的大鱼所经历的轮数为time_big,若干位于右边的较小的鱼所经历的轮数构成的列表为time_small_list(这些小鱼之间不会再互相吞吃,即time_small_list从左到右呈现非递减的取值)。

对于time_small_list中特定的time_smalltime_big的表达式为

time_big = max(time_big+1, time_small)

其中+1表示在之前得到time_big的基础上,吃掉小鱼还需要多花费1轮,time_small为小鱼之前经历的轮数,两者的较大值才是time_big的结果。

得到该结论之后,就很容易想到本问题可以使用逆序遍历的****单调栈来解决了:

  1. 栈中储存一个二元元组(num, time),分别为鱼的体积和该鱼所经历的轮数
  2. 逆序遍历原数组nums中的元素num,若
    1. 栈为空栈,或num小于等于栈顶元素储存鱼的体积,则该条鱼无法吃到任何一条鱼。
      • (num, 0)压入栈中
    2. num大于栈顶元素储存鱼的体积,则该大鱼可以吃掉若干栈顶的小鱼。
      • 初始化time_big = 0
      • 使用一个while循环,不断弹出栈顶小鱼,更新time_big = max(time_big+1, time_small)
      • while循环结束后,将(num, time_big)压入栈中

上述过程的核心代码为

for num in nums[::-1]:
    if len(stack) == 0 or stack[-1][0] >= num:
        stack.append((num, 0))
    else:
        time_big = 0
        while stack and stack[-1][0] < num:
            num_small, time_small = stack.pop()
            time_big = max(time_big+1, time_small)
        stack.append((num, time_big))

下面的图解展示了用单调栈解决该问题的过程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

我们发现我们的过程中在对于5 4 2 2 3的情况我们用4把后面3条鱼吃掉了,但实际上4在第2轮就会被5吃掉了。这实际上这并不影响答案的正确性,因为后面的小鱼2 2 3终究会被吃掉,不论是被4还是被5吃掉,都需要花费3轮,我们让4来做该操作,是让4代替5来吃鱼,后续的花费会在取最大值的过程中转换。

代码

Python

# 题目:【单调栈】Bilibili2021秋招-大鱼吃小鱼
# 作者:闭着眼睛学数理化
# 算法:单调栈
# 代码有看不懂的地方请直接在群上提问

# 输入数组大小,数组
n = input()
nums = list(map(int, input().split()))
# 初始化空的单调栈,栈中储存(num, time)这样一个二元组
stack = list()

# 逆序遍历nums中的每一个元素num
for num in nums[::-1]:
    # 空栈以及栈顶元素对应的鱼体积大于等于num的情况
    # 该分支语句其实可以不用单独列出,可以被else中的语句所包含
    # 但为了代码逻辑清晰,还是单独列出该分支
    if len(stack) == 0 or stack[-1][0] >= num:
        stack.append((num, 0))
    # 栈顶元素对应的鱼体积小于num的情况
    # 即num可以吃掉若干栈顶元素对应的鱼
    else:
        # 初始化该大鱼需要经历的轮数为0
        time_big = 0
        # 用一个while循环弹出若干栈顶的小鱼
        # 将其中所经历的轮数的最大值+1后赋值给time_big
        while stack and stack[-1][0] < num:
            # 弹出栈顶小鱼,其体积和经历的轮数分别为num_small, time_small
            num_small, time_small = stack.pop()
            time_big = max(time_big+1, time_small)
        # 该大鱼吃掉若干小鱼后,要将其体积和所经历的轮数重新压回栈顶
        stack.append((num, time_big))

# 退出循环后,栈中剩余的所有鱼所经历轮数的最大值,即为答案
print(max(time for num, time in stack))

Java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) {
            nums[i] = scanner.nextInt();
        }

        // Initialize an empty stack of pairs (num, time)
        Stack<Pair> stack = new Stack<>();

        for (int i = n - 1; i >= 0; i--) {
            int num = nums[i];

            if (stack.isEmpty() || stack.peek().num >= num) {
                stack.push(new Pair(num, 0));
            } else {
                int timeBig = 0;
                while (!stack.isEmpty() && stack.peek().num < num) {
                    Pair pair = stack.pop();
                    int numSmall = pair.num;
                    int timeSmall = pair.time;
                    timeBig = Math.max(timeBig + 1, timeSmall);
                }
                stack.push(new Pair(num, timeBig));
            }
        }

        int maxTime = 0;
        while (!stack.isEmpty()) {
            maxTime = Math.max(maxTime, stack.pop().time);
        }

        System.out.println(maxTime);
    }

    static class Pair {
        int num;
        int time;

        Pair(int num, int time) {
            this.num = num;
            this.time = time;
        }
    }
}

C++

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

struct Pair {
    int num;
    int time;

    Pair(int num, int time) : num(num), time(time) {}
};

int main() {
    int n;
    cin >> n;

    vector<int> nums(n);
    for (int i = 0; i < n; ++i) {
        cin >> nums[i];
    }

    stack<Pair> stack;

    for (int i = n - 1; i >= 0; --i) {
        int num = nums[i];

        if (stack.empty() || stack.top().num >= num) {
            stack.push(Pair(num, 0));
        } else {
            int timeBig = 0;
            while (!stack.empty() && stack.top().num < num) {
                Pair pair = stack.top();
                stack.pop();
                int numSmall = pair.num;
                int timeSmall = pair.time;
                timeBig = max(timeBig + 1, timeSmall);
            }
            stack.push(Pair(num, timeBig));
        }
    }

    int maxTime = 0;
    while (!stack.empty()) {
        maxTime = max(maxTime, stack.top().time);
        stack.pop();
    }

    cout << maxTime << endl;

    return 0;
}

时空复杂度

时间复杂度:O(N)。每个元素至多只需出入栈一次。

空间复杂度:O(N)。单调栈所占空间。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值