文章目录
- 题目描述与示例
- 题目描述
- 输入描述
- 输出描述
- 示例一
- 输入
- 输出
- 说明
- 示例二
- 输入
- 输出
- 说明
- 解题思路
- 代码
- Python
- Java
- C++
- 时空复杂度
- 华为OD算法/大厂面试高频题算法练习冲刺训练
题目描述与示例
题目描述
小明最近喜欢上了俄罗斯套娃、大鱼吃小鱼这些大的包住小的类型的游戏。
于是小明爸爸给小明做了一个特别版的大鱼吃小鱼游戏,他希望通过这个游戏能够近一步提高小明的智商。
游戏规则如下:
现在有N
条鱼,每条鱼的体积为Ai
,从左到右排成一排。A
数组是一个排列。
小明每轮可以执行一次大鱼吃小鱼的操作。一次大鱼吃小鱼的操作:对于每条鱼,它在每一次操作时会吃掉右边比自己小的第一条鱼。
值得注意的是,在一次操作中,每条鱼吃比自己小的鱼的时候是同时发生的。
举一个例子,假设现在有三条鱼,体积为分别[5,4,3]
,5
吃4
,4
吃3
,一次操作后就剩下[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_small
,time_big
的表达式为
time_big = max(time_big+1, time_small)
其中+1
表示在之前得到time_big
的基础上,吃掉小鱼还需要多花费1
轮,time_small
为小鱼之前经历的轮数,两者的较大值才是time_big
的结果。
得到该结论之后,就很容易想到本问题可以使用逆序遍历的****单调栈来解决了:
- 栈中储存一个二元元组
(num, time)
,分别为鱼的体积和该鱼所经历的轮数 - 逆序遍历原数组
nums
中的元素num
,若- 栈为空栈,或
num
小于等于栈顶元素储存鱼的体积,则该条鱼无法吃到任何一条鱼。- 将
(num, 0)
压入栈中
- 将
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
了解更多