leetcode42,接雨水,思路清晰,由简入繁讲解,java语言
作者:互联网
记录自己刷题弄的一些思路
先列出问题
给定 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 个单位的雨水(蓝色部分表示雨水)。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/trapping-rain-water
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题只要有4个解法,分别是暴力解,动态规划,双指针(面试官比较想看到的答案),以及栈(单调递减栈)。
首先必须会暴力解,很多人直接一上来就想双指针,动态规划等等,只能说除非你做了很多类似的题,一看就能想到的话,不然你很难想出最优解。
这道题先用暴力解解出来,后来才发现暴力解有很多重复操作,因此可以用动态规划来记录一些以前操作的解,这就是一个思路的提升。再到双指针其实是动态规范的一种更好的优化。最后一种解法单调递减栈是比较有意思的解法。
(提示:从暴力解–动态规划–双指针都是垂直方向积水的计算)
1.暴力解
从左到右遍历数组,每一个位置 i 能否积水(或者说积水多少)取决于其左边最大的边界和其右边最大边界来决定的,当左边最大边界及右边最大边界决定后,根据木桶原理,左右边界最小值按理应该是该位置能积水多少,但是别忘了还得减去该位置的高度,因为假如左右边界最小值是2,但是该柱子的高度也是2,就没法积水。
public int trap(int[] height) {
if(height.length <=2) {
return 0;
}
int left_max;
int right_max;
int sum = 0;
for(int i=1;i<height.length;i++){
left_max = height[i];
right_max = height[i];
for(int j=i-1;j>=0;j--){
left_max = Math.max(height[j],left_max);
}
for(int j=i+1;j<height.length;j++){
right_max = Math.max(height[j],right_max);
}
sum += Math.min(left_max,right_max) - height[i];
}
return sum;
}
时间复杂度O(n^2)
空间复杂度O(1)
2.动态规划
理解了上述暴力解那就很容易地想到,每一步都要去找其左右边界最大值是一个重复过程,例如某个位置 i ,其左边边界最大值无非就是 i-1 位置左边边界的最大值 和 i 处高度的相比,即得出下面状态转移过程(使用动态规划,只要有状态转移方程那就很简单写出相应语言解法)
Left_max[i] = Max( Left_max[i-1] , heigth[i] )
同理右边边界最大值状态转移方程
Right_max[j] = Max( Right_max[j+1] , heigth[j] )
使用两个数组来充当备忘录。
public int trap(int[] height) {
if(height.length <=2) {
return 0;
}
int size = height.length;
int[] left_max_arr = new int[size];
int[] right_max_arr = new int[size];
int sum = 0;
left_max_arr[0] = height[0];//初始条件
for(int i=1;i<size;i++){
left_max_arr[i] = Math.max(left_max_arr[i-1], height[i]);
}
right_max_arr[size-1] = height[size-1];//初始条件
for(int i=size-2;i>=0;i--){
right_max_arr[i] = Math.max(right_max_arr[i+1], height[i]);
}
for(int i=0;i<size;i++){
sum += Math.min(left_max_arr[i], right_max_arr[i]) - height[i];
}
return sum;
}
时间复杂度O(n)
空间复杂度O(n)
3.双指针方法(最优解)
从动态规划解法可以看出只要当left_max_arr[i] < right_max_arr[i](木桶原理),该 i 位置积水量是由left_max_arr[i]决定的,反过来当left_max_arr[i] > right_max_arr[i](木桶原理),该 i 位置积水量是由right_max_arr[i]决定的。
因此都不需要两个数组来记录左边最大边界和右边最大边界,只需要两个指针指向数组的left端和right端,同时两个变量记录left_max,和right_max。左右指针移动的规则是当height[left] < height[right],积水量看left_max,并且左指针left往right移动。
或许读者会问,为什么是这样的移动的规则呢?想一想,前面说的当left_max_arr[i] < right_max_arr[i](木桶原理),该 i 位置积水量是由left_max_arr[i]决定。那么当height[left] < height[right],left_max_arr[left] 必然小于right_max_arr[left] ,因为right_max_arr[left] >= right_max_arr[right] >= height[right];
这里比较绕,重新再打一次数学表达式来表示
(
动态规划解法可以看出当left_max_arr[i] < right_max_arr[i](木桶原理),该 i 位置积水量是由left_max_arr[i]决定的,所以下面的各种思路都是为了得到当height[left] < height[right],left_max_arr[left] <= right_max_arr[left],移动左指针就好;
当height[left] < height[right]时:
left_max_arr[left] = Max(left_max_arr[left-1],height[left]);
而right_max_arr[left] = Max(right_max_arr[left+1],height[left]));
因此left<right时,right_max_arr[left] > right_max_arr[right]
而right_max_arr[right] =Max(right_max_arr[right+1],height[right])) ,并且height[right] > height[left] ;
right_max_arr[left] > right_max_arr[right] >= height[right] > height[left]
因为left,right一开始从左右两端往之间靠,上一次的移动也是采用该规则,就有必然有
left_max_arr[left] <= right_max_arr[left];)
public int trap(int[] height) {
if(height.length <=2) {
return 0;
}
int left = 0;
int right = height.length-1;
int left_max = 0;
int right_max = 0;
int sum = 0;
while(left<right){
if(height[left] < height[right]){
left_max = Math.max(left_max,height[left]);
sum += left_max - height[left];
left++;
} else {
right_max = Math.max(right_max,height[right]);
sum += right_max - height[right];
right--;
}
}
return sum;
}
时间复杂度O(n)
空间复杂度O(1)
4.栈思想
上面的思路都是基于垂直的积水,即每个位置 i 的积水 = “某个计算的高度” - 该位置柱子的高度;其实也可以基于横向的计算积水,积水总量 = ∑ (左右边界积水高度极小值 *边界的距离)。这个直接上代码才能说得通
public int trap(int[] height) {
if(height.length <=2) {
return 0;
}
Stack<Integer> stack = new Stack<Integer>();
int sum = 0;
for(int i=0;i<height.length;i++){
//产生积水的位置是低洼处,也就是此位置的高度比前一个位置高度要大
while(!stack.isEmpty() && height[stack.peek()] <= height[i]){
int lastIndex = stack.pop();
if(!stack.isEmpty()){
//横向积水高度
int hei = Math.min(height[stack.peek()],height[i]) - height[lastIndex];
//横向积水宽度
int distance = i - stack.peek() - 1;
sum += (distance*hei);
}
}
stack.push(i);
}
return sum;
}
标签:arr,right,java,简入,int,max,height,leetcode42,left 来源: https://blog.csdn.net/dogHuaMing/article/details/111932761