·题目描述
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5] 输出:9
·解题思路
————————暴力求解(最直接的想法)——————
利用指针遍历,left和right两个指针left从0到n-2,right 在left右边。当right移动遇见元素大于或者等于原始left元素的时候,说明形成凹陷,可以载雨水。
这个的问题在于,当最初始的left为最大元素,而right到达边界也不能大于或等于原始left元素,但是载该内部有小凹陷可以存在雨水,也就是需要对于边界条件进行处理。
解决办法,引入了有边界寻找的条件判断。当可以寻找到右边界,那么就是简单处理。如果寻找不到右边界,寻找从边界条件处left往后的最大元素(不包括left),将最大元素作为right边界计算凹形面积。再left = right 迭代。
为什么要用最大元素作为边界条件呢?这是以为从右边界往前,最大元素会阻挡凹陷。
·代码java
class Solution {
public int trap(int[] height) {
int n = height.length;
int left = 0;
int area = 0;
while(left < n - 1){
if(height[left] < height[left + 1]){
left ++;
continue;
}
int count = 0;
int right = left + 1;
boolean fond = false;
while(right < n){
if(height[right] >= height[left]){
fond = true;
break;
}
count += height[right];
right ++;
}
if(fond){
int H = Math.min(height[left], height[right]);
int W = right - left - 1;
area += H * W - count;
left = right ;
}
else {
int MaxIndex = left + 1;
for(int i = left + 1; i < n; i++) {
if (height[i] > height[MaxIndex]) {
MaxIndex = i;
}
}
right = MaxIndex;
count = 0;
for(int j = left + 1; j < right; j++){
count += height[j];
}
int H = Math.min(height[left], height[right]);
int W = right - left - 1;
area += H * W - count;
left = right ;
}
}
return area;
}
}
——————官方的动态规划——————
动态规划的原理在于:从左往右将小元素置为左边最大的元素;再从右往左,将小元素置为右边最大的元素。两次扫描的重叠区域再减去原有的数组就是可以盛水的区域。
·代码:
public int trap(int[] height) {
int n = height.length;
int area = 0;
int[] leftMax = new int[n], 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]);
}
for(int i = 0 ; i < n ; i ++){
area += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return area;
}
————————将两个数组该为双指针问题——————
从上述动态规划的算法中可以发现,最后的area叠加
area += Math.min(leftMax[i], rightMax[i]) - height[i];
那么可以利用双指针将min计算步骤改为
(height[left] < height[right])
所以最后代码:
public int trap(int[] height) {
int n = height.length;
int area = 0;
int left =0, right = n - 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]) {
area += leftMax - height[left];
left++;
} else {
area += rightMax - height[right];
right--;
}
}
return area;
}
---------单调栈---------
使用单调栈解决该问题,每次寻找大于左边界的有边界。其中凹下去的面积就是可以承接雨水的面积,在这其中关键的问题在于,存在大凹陷和小凹陷的时候,如何正确的计算面积。
关键点:
单调栈:只有元素小于栈顶元素的时候,元素入栈;当元素大于栈顶元素的时候,构成凹陷,元素出栈;由于是获取元素的右边更大元素,因此使用单调减栈。
计算面积:首先处理大小凹陷并存的情况(例如:3,2,0,2,3),存在两个可以存雨水的地方。按照栈的处理顺序应该是闲处理(2,0,2)这个凹陷,只要在处理凹陷后保持大凹陷为(3,2,3)表示,大凹陷有一个高度为2的底面。
W= i - left - 1 left表示左端位置
H= Math.min(heigh[i], height[left]) - height[top] height[top]表示底面
import java.util.Stack;
class Solution {
public int trap(int[] height) {
int n = height.length;
Stack<Integer> index = new Stack<Integer>();
int ans = 0;
for(int i = 0; i < n; i++){
if(index.isEmpty() || height[i] < height[index.peek()]) index.push(i);
while(!index.isEmpty() && height[index.peek()] < height[i]){
int top = index.pop();
if(index.isEmpty()) break;
int left = index.peek();
int W = i - left - 1;
int H = Math.min(height[i], height[left]) - height[top];
ans += W * H;
}
index.push(i);
}
return ans;
}
}
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.trap(new int[]{4,2,0,3,2,5}));
}
}