当青训营遇上码上掘金
题目:
-
攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
输入案例:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。、
👨🏫 题解传送门
👨🏫 核心思想
咋一看,整体求解有点难,那就可以分开来求,分析每一根柱子上面可以攒多少青豆,类似于微元法 根据短板效应 得某一位置上能攒的青豆数 主要受 该位置两边的最高柱子 中的较矮的 柱子高度 限制
👨🏫 暴力求解的主要思路 O(n^2):
- 求该位置左边最高的柱子的高度 lMax
- 求该位置右边最高的柱子的高度 rMax
- min(lmax,Rmax) - 该位置的height = 青豆数
注意:当该位置的柱子是左或者右最高柱子的时候,攒不了青豆 并且左右边界柱子也是攒不了青豆的,可以直接跳过
👨🏫 记忆表优化 O(n)
对于暴力求解的方法进行分析,就会发现在求左右最高柱子的时候存在大量的重复计算,这部分是最容易想到可以优化的地方,以空间换时间的办法之一,就是开一个记忆表存放某位置的左右最高柱子高度,建立层层依赖的关系 例: 左边最高柱子的高度 第一个是本身,第二个就是本身与第一个的高度比较大小,第三个就是本身与第二个比较大小即可...... 这样下来求最高柱子便可以省掉重复的操作
主要思路:
- 循环填左右最高柱子的记忆表
- 根据记忆表求出每个位置的青豆数(特判:当最高柱子 == 本身高度 时,攒的青豆数为 0,可以直接跳过)
👨🏫 拓展
🐷 单调栈:元素具有单调性的栈
🐷 单调栈求解 O(n):
上边两个方法都是 分开求每一个横轴单位上(列)的青豆数 而单调栈是 按 层 来求某一个凹槽上可以攒的青豆 凹槽特性,柱子高度先单调递减,再单调递增
主要思路:
-
维护一个单调递减的栈,栈存柱子的下边,当前柱子的高度比栈顶柱子的高度 小,入栈
-
否则,即是凹槽,栈顶元素出栈(注意相同的元素)
-
此凹槽的青豆数 = (min(栈顶柱子的高度,当前柱子的高度)- 出栈柱子的高度)* (当前柱子的下标 - 栈顶柱子的下标 - 1)
// ...下标-1 : 例: 右柱 3,左柱 1, 3-1 = 2,但实际只有中间的 2柱 上能攒豆,so: 3-1-1 = 1
-
当前柱子进栈
-
循环往复,直到栈空,返回结果
/**
* 单调栈求解法
*
* @param height 高度数组
* @return 攒的青豆数
*/
public static int getBeans3(int[] height)
{
// 思路:横向青豆,就是求 凹槽 先递增后递减
int res = 0;
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < height.length; i++)
{
while (!stack.isEmpty() && height[i] > height[stack.peek()])
{
int top = stack.pop();
while (!stack.isEmpty() && height[top] == height[stack.peek()])
{
stack.pop();// 需要考虑重复元素
}
if (!stack.isEmpty())
{
int min = Math.min(height[i], height[stack.peek()]);// 取当前遍历值 与 栈顶元素的较小值
res += (min - height[top]) * (i - stack.peek() - 1);// -1 是因为在中间才能攒豆
}
}
stack.push(i);
}
return res;