42.接雨水.png

暴力解法

确定每个列所能装载的水容量,累加即可得到结果。
每个列所能装载的水容量步骤如下:

  1. 获得左边最高的高度 maxLeft。
  2. 获得右边最高的高度 maxRight。
  3. 得到最小值 minH = Math.max(maxLeft, maxRight);
  4. 当前柱子所能承载的水容量 = minH - 柱子高度

注意,最左和最右柱子是不会装载水,所以在遍历时剔除。

  1. public int trap(int[] height) {
  2. // 解法一:暴力解
  3. int res = 0;
  4. // 注意,最左和最右是不会盛水的
  5. for (int i = 1; i < height.length-1; i++) {
  6. // 找左边最高的位置
  7. int maxLeft = 0;
  8. for (int j = i-1; j >= 0; j--) {
  9. maxLeft = Math.max(maxLeft, height[j]);
  10. }
  11. // 找右边最高的位置
  12. int maxRight = 0;
  13. for (int j = i + 1; j < height.length; j++) {
  14. maxRight = Math.max(maxRight, height[j]);
  15. }
  16. int minHeight = Math.min(maxLeft, maxRight);
  17. if (minHeight > height[i]) {
  18. res += (minHeight - height[i]);
  19. }
  20. }
  21. return res;
  22. }

动态规划

暴力解法中我们每次都需要向左、向右找出柱子最高的高度,我们不妨使用一个数组将柱子 i 左边最高高度和右边最高高度存起来,递推式如下:

  1. int[] maxLeft = new int[length];
  2. int[] maxRight = new int[length];
  3. maxLeft = Math.max(maxLeft[i-1], height[i-1]);
  4. maxRight = Math.max(maxRight[i+1], height[i+1]);

双指针

动态规划中,我们常常可以对空间复杂度进一步优化。
双指针法真的妙,那么如何理解双指针法呢?听我来给你捋一捋。(捋的过程和原解中的C++在细节方面的处理是有出入的)
我们先明确几个变量的意思:
leftmax:左边的最大值,它是从左往右遍历找到的 rightmax:右边的最大值,它是从右往左遍历找到的 left:从左往右处理的当前下标 right:从右往左处理的当前下标
定理一:在某个位置i处,它能存的水,取决于它左右两边的最大值中较小的一个。
定理二:当我们从左往右处理到left下标时,左边的最大值leftmax对它而言是可信的,但rightmax对它而言是不可信的。(见下图,由于中间状况未知,对于left下标而言,rightmax未必就是它右边最大的值)
定理三:当我们从右往左处理到right下标时,右边的最大值rightmax对它而言是可信的,但leftmax对它而言是不可信的。
rightmax leftmax | | | |_ ?????????????????????? | |
| |
|
| |
left right
对于位置left而言,它左边最大值一定是left_max,右边最大值“大于等于”right_max,这时候,如果left_max<right_max成立,那么它就知道自己能存多少水了。无论右边将来会不会出现更大的right_max,都不影响这个结果。 所以当left_max<right_max时,我们就希望去处理left下标,反之,我们希望去处理right下标。

  1. class Solution:
  2. def trap(self, height: List[int]) -> int:
  3. left=0
  4. right=len(height)-1
  5. left_max=right_max=0
  6. ans=0
  7. while left<=right:
  8. if left_max<right_max:
  9. ans+=max(0,left_max-height[left])
  10. left_max=max(left_max,height[left])
  11. left+=1
  12. else:
  13. ans+=max(0,right_max-height[right])
  14. right_max=max(right_max,height[right])
  15. right-=1
  16. return ans

单调递减栈

image.png
image.png

  1. // 单调递减栈
  2. public int trap(int[] height) {
  3. int res = 0;
  4. Deque<Integer> stack = new ArrayDeque<>();
  5. for (int i = 0; i < height.length; i++) {
  6. while (!stack.isEmpty() && height[stack.peekLast()] < height[i]) {
  7. // 右柱子大小栈顶元素,可计算栈顶元素对应的部分雨水容量
  8. int popIndex = stack.pollLast();
  9. if (stack.isEmpty()) break; // 栈为空,说明已经计算完成
  10. int width = i - stack.peekLast() - 1;
  11. int h = Math.min(height[stack.peekLast()], height[i]);
  12. res += width * (h - height[popIndex]);
  13. }
  14. stack.addLast(i);
  15. }
  16. return res;
  17. }