当青训营遇上码上掘金 本题来源于经典面试题:接雨水
方法一:双指针暴力
观察可知,每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中较矮的那个柱子的高度 - 当前柱子高度。
int len = height.length;
int res=0;
for(int i=1;i<len-1;++i){
int leftMax = Integer.MIN_VALUE;
int rightMax = Integer.MIN_VALUE;
// 找左边的最高点
for(int j=0;j<i;++j){
if(height[j]>leftMax){
leftMax=height[j];
}
}
// 找右边的最高点
for(int j=i+1;j<len;++j){
if(height[j]>rightMax){
rightMax=height[j];
}
}
int cur = Math.min(leftMax,rightMax)-height[i];
if(cur>0){
res+=cur;
}
}
return res;
时间复杂度O(N^2)
方法二 动态规划
可以发现,在方法一中,每求一列的左右柱子最大高度都需要遍历整个数组,做了很多重复的计算。 实际上,【求取某个位置左边的最大高度】的这种问题,是可以划分成若干个子问题的,并且子问题相互独立。
所以我们可以使用动态规划去解决这个问题。
我们只关心 i 位置左右的最大柱子高度,所以,为了避免重复计算,问题就转换成了,求两个目标数组leftMax[]和rightMax[],分别存储左边最大高度和右边最大高度。
这样,此题就被我们转换成了一个非常经典的动态规划入门题目。
int len = height.length;
int res=0;
int[] leftMax = new int[len];
int[] rightMax = new int[len];
// 动态规划解出每个位置左侧的最大高度
for(int i=1;i<len;i++){
leftMax[i]=Math.max(leftMax[i-1],height[i-1]);
}
for(int i=len-2;i>=0;i--){
rightMax[i]=Math.max(rightMax[i+1],height[i+1]);
}
for(int i=1;i<len-1;i++){
int cur = Math.min(leftMax[i],rightMax[i])-height[i];
if(cur>0){
res+=cur;
}
}
return res;
时间复杂度O(N)
方法三 单调栈
上面的做法,都是以 列 为单位去计算。其实还可以以行为单位去计算。其实,就是一个凹槽一个凹槽地计算。我们将凹槽看成矩形,大凹槽里还可以存在一个小凹槽,所以我们计算每个凹槽,相加即为最后结果。所有的凹槽,都是 高-低-高 的排列,
如果我们每次都单独得去判断 i 位置是不是某个凹槽的底部,并寻找凹槽的边界,我们就又回到了方法1中的那种暴力解法。
哪里可以优化呢?
我们可以利用单调栈的性质。我们保证递减的顺序入栈(即保证入栈的元素小于栈顶),当即将入栈的元素大于栈顶时,我们就找到了一个凹槽 ,栈顶就是凹槽的底部,即将入栈的元素和栈顶的下一个元素就是凹槽的两侧。
// 保持递减入栈 栈内存放下标 按行计算
int res = 0;
ArrayDeque<Integer> ad = new ArrayDeque <>();
for(int i=0;i<height.length;i++){
// 入栈能保持递减
if(ad.isEmpty() || height[ad.peekLast()] >height[i]){
ad.addLast(i);
continue;
}
if(height[ad.peekLast()]==height[i]){
ad.pollLast();
ad.addLast(i);
continue;
}
// 当前i位置比栈顶还高,栈顶就是凹槽,所以栈顶位置的上方一定存在雨水
while(!ad.isEmpty() && height[ad.peekLast()]<height[i]){
// 栈顶就是最底部的了 当前要计算的雨水都在最底部的上面 计算完之后就不会再算入了
// 所以要弹出
int low = ad.pollLast();
// 没有左边的柱子,接不了雨水
if(!ad.isEmpty()){
// 左边的柱子 本次计算完后,左边的柱子还有可能成为底部,需要计算,所以不能弹出
int left = ad.peekLast();
// 宽度
int w = i - left-1;
// 高度
int h = Math.min(height[i],height[left])-height[low];
res+=w*h;
}
}
ad.addLast(i);
}
return res;
注意,java的Stack性能很差,我们这里使用Deque作为栈。 时间复杂度O(N)