直方图的水量

面试题 17.21. Volume of Histogram LCCI

先看题目描述

image-20210403091905925

大意就是给定一个数组 height 表示的直方图,问最多能存多少水量

算法和思路

动态规划

构建两个数组 leftMax 和 rightMax,leftMax[i] 表示 height[0:i] 的高度最大值,rightMax[i] 表示 height[i:len - 1] 的高度最大值

  • leftMax 的状态转移方程为 leftMax[i] = max{leftMax[i - 1],height[i]}
  • rightMax 的状态转移方程为 rightMax[i] = max{rightMax[i + 1],height[i]}

对于下标 i,该处对最大存水量的贡献为 min{leftMax[i],rightMax[i]} - height[i],因此遍历 height 数组,依次计算每处下标对最大存水量的贡献,将贡献累加得到结果即可

单调栈

维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组 height 中元素递减

从左到右遍历数组,遍历到下标 i 时,如果栈内至少有两个元素,记栈顶元素为 top,top 之下的元素为 left,则得到一个可以接雨水的区域,该区域的高度为 min{height[left],height[i]} - height[top],宽度为 i - left - 1,根据高度和宽度可求出该接雨水区域的面积

将 top 出栈,在对 top 计算能接的水量后,left 变为新的 top;重复上述过程直至栈顶下标对应高度大于等于 height[i] 或栈为空

对下标 i 处计算能接的水的量之后,将 i 入栈,继续遍历后面下标,计算能接的水的量,遍历结束后即可得到结果

ps:个人认为这两种思路的一个主要区别在于第一个是竖着求最大存水量的,另一个是横着求最大存水量的

算法源码

动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) {
return 0;
}
int[] leftMax = new int[n];
int[] rightMax = new int[n];
leftMax[0] = height[0];
rightMax[n - 1] = height[n - 1];
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 1; i < n; i++) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}

单调栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int trap(int[] height) {
int ans = 0;
Deque<Integer> stack = new LinkedList<Integer>();
int n = height.length;
for (int i = 0; i < n; ++i) {
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int top = stack.pop();
if (stack.isEmpty()) {
break;
}
int left = stack.peek();
int currWidth = i - left - 1;
int currHeight = Math.min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stack.push(i);
}
return ans;
}
}