当青训营遇上码上掘金
题目-- 攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
示例
- 输入:height = [5,0,2,1,4,0,1,0,3]
- 输出:17
- 解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
方法1:中心拓展法
思路
本方法是按列来计算:
如果按照列来计算的话,宽度一定是1,我们只需要把每一列的青豆的高度求出来就可以了。
怎么求高度?当前列青豆的高度取决于其左侧最高柱子和其右侧最高柱子中的最小高度,使用两者中的最小高度减去当前柱子的高度,就是当前列青豆的高度。
代码
class Solution {
public int trap(int[] height) {
int ans = 0;
for (int i = 1; i < height.length - 1; i++) {
int l = height[i];
int r = height[i];
for (int j = 0; j < i; j++) {
// 寻找左边最大值
l = Math.max(l, height[j]);
}
for (int j = i + 1; j < height.length; j++) {
// 寻找右侧最大值
r = Math.max(r, height[j]);
}
ans += Math.min(l, r) - height[i];
}
return ans;
}
}
复杂度
-
时间复杂度:O(n*n)
-
空间复杂度:O(1)
方法2:单调栈
思路
什么是单调栈?
栈中存放的数据是有序的,分为单调递增栈和单调递减栈(下面都是以栈顶元素为基准):
- 单调递增栈:单调递增栈就是从栈顶到栈底数据是从小到大(栈顶最小)
- 单调递减栈:单调递减栈就是从栈顶到栈底数据是从大到小(栈顶最大)
单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是只需要遍历一次。
本题中单调栈是按照行方向来计算青豆的。本质上是寻找当前柱子左边和右边第一个大于该柱子的柱子。
为了方便计算攒青豆区域的宽度,栈中应该存放柱子的下标,通过下标我们也可以从height数组中得到柱子的高度。
单调栈内元素的顺序: 这里我们要使用递增栈(栈顶元素最小) ,因为如果发现添加的柱子高度大于栈顶元素了,此时就出现凹槽了,栈顶元素就是凹槽底部的柱子,次栈顶就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
遍历数组入栈时考虑三种情况:
- 当前遍历的元素小于栈顶元素:此时当前遍历的元素直接入栈。因为栈顶元素是栈中最小的,如果当前元素比它还小,说明不满足题目的要求,还要继续往后搜索。
- 当前遍历的元素等于栈顶元素:直接将当前元素入栈。因为在计算面积的时候相邻的两个元素高度相同,所以凹槽青豆的高度就是0,对应的面积也是0。
- 当前遍历的元素大于栈顶元素:此时我们就应该计算青豆区域面积。其实就是用栈顶和次栈顶以及要入栈的三个元素来攒青豆,因为此时次栈顶元素和入栈元素都高于栈顶元素(凹槽),形成了攒青豆区域。
代码
class Solution {
public int trap(int[] height) {
Deque<Integer> stack = new ArrayDeque<>();
int ans = 0;
for (int i = 0; i < height.length; i++) {
// 下面的判断可以省略
if (!stack.isEmpty() && height[i] == height[stack.peek()]) stack.pop();
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
// 遍历元素大于栈顶元素
// 弹出栈顶元素,求当前凹槽的面积
int temp = stack.pop();
if (stack.isEmpty()) break;
// 求高度
int h = Math.min(height[i], height[stack.peek()]) - height[temp];
// 求宽度
int w = i - stack.peek() - 1;
ans += h * w;
}
stack.push(i);
}
return ans;
}
}
复杂度
-
时间复杂度:O(n)
-
空间复杂度:O(n)