问题转化
最小子问题:每根柱子的水量由其左边最高的柱子
和其右边最高的柱子
决定
短板效应:对左右的最高柱子
取其较小值,与当前柱子比较高度
求:数组任一边界
与当前柱子
围成的区间中的最大值
法一:动态规划
动态规划:解决重复子问题
leftMax[i]
:前 i 个元素中,最大的值rightMax[i]
:后 i 个元素中,最大的值
//官方题解
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) {
return 0;
}
int[] leftMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
int[] rightMax = new int[n];
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
法二:双指针
注意到,法一状态转移时,dp[ i ] 只依赖于 dp[ i-1 ],因此我们可以使用变量来代替 dp 数组。
//官方题解
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}
法三:单调栈(分层)
- 枚举每根柱子
- 维持一个单调非增的栈
- <= 栈顶:入栈
- > 栈顶:
-
小于 height[ i ] 的全都出栈,最后 i 入栈
-
分层:每出栈一个 top,填补【左:left】和【右:i】之间的面积,看作先处理
min(left,i)
和top
之间层的面积。宽度:i - left
;高度:min(left,i) - top
-
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;
}
}
法四:双指针(分层)
- 指针从两边向中间移动,遇到
next柱高
>当前层high
停下,当前层遍历结束 - 计算面积:【宽:right - left +1】*【高:high=1】
- 边界条件:
left <= right
public class Solution {
public int trap(int[] height)
{
int sum = 0;
for(int i = 0;i<height.length;i++) {
sum += height[i];
}//求数组总和
int volume = 0; // 总体积和高度初始化
int high = 1;
int size = height.length;
int left = 0; // 双指针初始化
int right = size - 1;
while (left <= right) {
while (left <= right && height[left] < high) {
left++;
}
while (left <= right && height[right] < high) {
right--;
}
volume += right - left + 1; // 每一层的容量都加起来
high++; // 高度加一
}
return volume -sum; // 总体积减去柱子体积,即雨水总量
}
}