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


    题目描述与示例

    题目描述

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

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

    游戏规则如下:

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

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

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

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

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

    输入描述

    第一行输入长度N

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

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

    输出描述

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

    示例一

    输入

    3
    1 2 3
    
    • 1
    • 2

    输出

    0
    
    • 1

    说明

    无需操作A数组。

    示例二

    输入

    6
    4 3 2 3 2 1
    
    • 1
    • 2

    输出

    2
    
    • 1

    说明

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

    解题思路

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

    对于数组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

    其中+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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    在这里插入图片描述

    我们发现我们的过程中在对于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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    C++

    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    时空复杂度

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

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


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

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

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

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

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

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

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

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

  • 相关阅读:
    transformers PreTrainedTokenizer类
    【Swift 60秒】47 - Functions:Summary
    JDK锁优化
    开源数据库 OpenGauss 的 SQL 解析源码分析
    打通“隔墙”!浅析低代码超强的整合能力
    考研408-计算机网络 第二章-物理层学习笔记及习题
    【2022】【论文笔记】基于激光直写氧化石墨烯纸的超薄THz偏转——
    视频批量高效剪辑,轻松翻转视频画面,支持将视频画面进行逆时针90度翻转。
    Spring JdbcTemplate基本使用
    Django有3种视图与说明
  • 原文地址:https://blog.csdn.net/weixin_48157259/article/details/134502718