给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [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
思路一:暴力法
其实刚看到这道题暴力法也有点麻烦,因为不知道怎么去暴力。每一个位置积水的高度=左右两边高度的最小值 - 该位置的高度,这个可以理解的话就很容易了。我们可以遍历每一个高度,求左边的最大值和右边的最大值,求得每一个位置的积水累加便可。时间复杂度为O(n^2)。
暴力法-参考代码:
class Solution
{
//暴力法
public int trap(int[] height)
{
int ans=0;
//遍历每一个高度
for(int i=0;i<height.length;i++)
{
//求该位置左右两边的最大值
int leftMax=height[i];
int rightMax=height[i];
for(int j=i-1;j>=0;j--)
leftMax=Math.max(leftMax, height[j]);
for(int j=i+1;j<height.length;j++)
rightMax=Math.max(rightMax, height[j]);
//累加每个位置的积水
ans+=Math.min(leftMax, rightMax)-height[i];
}
return ans;
}
}
思路二:利用类似前缀和的思想对暴力法进行优化
暴力法的复杂度主要来源于遍历每一个位置和求左右两边的最大值,遍历每一个位置这个是没有办法优化的,只能从求左右两边的最大值上优化。由于每个位置左右两边的最大值时固定不变的。所以我们可以直接利用类似前缀和思想的方法先将它们存到数组里。遍历每个位置的时候直接取出来。时间复杂度可降为O(n)。
暴力法优化-参考代码:
class Solution {
//暴力法的优化
public int trap(int[] height)
{
//记录数组记录每个位置左边的最大值
int[] leftMaxs=new int[height.length];
int lMax=0;
for(int i=0;i<height.length;i++)
{
if(height[i]>lMax)
lMax=height[i];
leftMaxs[i]=lMax;
}
//记录数组记录每个位置右边的最大值
int[] rightMaxs=new int[height.length];
int rMax=0;
for(int i=height.length-1;i>=0;i--)
{
if(height[i]>rMax)
rMax=height[i];
rightMaxs[i]=rMax;
}
//统计每个位置能放多少雨水
int ans=0;
for(int i=0;i<height.length;i++)
ans+=Math.min(leftMaxs[i], rightMaxs[i])-height[i];
return ans;
}
}
思路三:双指针
我们每次求一个位置的积水,都要考虑左右两边的最大值。如果我们确定了右边的最大值大于左边的最大值,便可以只考虑左边的最大值。反之同理。有点难理解,直接上核心代码
核心代码:
//遍历所有的位置
while(left<right)
{
//当左指针对应的高度比右边的低时,需要考虑左边的最大值
if(height[left]<height[right])
{
//当左指针对应的高度大于左边的最大值,不可能会有水,更新左边的最大值
if(height[left]>leftMax)
leftMax=height[left];
//否则求该位置能装的水
else
ans+=leftMax-height[left];
left++;
}
//与上同理
else
{
if(height[right]>rightMax)
rightMax=height[right];
else
ans+=rightMax-height[right];
right--;
}
}
return ans;
双指针-参考代码:
class Solution
{
//双指针
public int trap(int[] height)
{
if(height.length<=1)
return 0;
int ans=0;
int left=0,right=height.length-1;//左右指针
int leftMax=height[0],rightMax=height[height.length-1];//指针位置左右两边的最大值
while(left<right)
{
//当左指针对应的高度比右边的低时,需要考虑左边的最大值
if(height[left]<height[right])
{
//当左指针对应的高度大于左边的最大值,不可能会有水,更新左边的最大值
if(height[left]>leftMax)
leftMax=height[left];
//否则求该位置能装的水
else
ans+=leftMax-height[left];
left++;
}
//与上同理
else
{
if(height[right]>rightMax)
rightMax=height[right];
else
ans+=rightMax-height[right];
right--;
}
}
return ans;
}
}