|
| 1 | +> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。 |
| 2 | +> |
| 3 | +> 个人博客:https://www.zhangxiaoshuai.fun |
| 4 | +
|
| 5 | +**本题选择leetcode中第42题,hard级别,目前通过率50.8%#** |
| 6 | + |
| 7 | +### 题目描述: |
| 8 | + |
| 9 | +```txt |
| 10 | +给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 |
| 11 | +示例: |
| 12 | +输入: [0,1,0,2,1,0,1,3,2,1,2,1] |
| 13 | +输出: 6 |
| 14 | +``` |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +### 题目分析: |
| 19 | + |
| 20 | +通过题意,一个“凹槽”可以存储的雨水的容量取决于它前后的柱子。 |
| 21 | + |
| 22 | +### 解法一: |
| 23 | + |
| 24 | +仔细想想,其实这跟木桶原理是有相似的地方的,针对每一个柱子,我们需要往前看和往后看,分别找出当前柱子前面最高的柱子和后面最高的柱子。 |
| 25 | + |
| 26 | +这里有**三种情况**我们需要了解: |
| 27 | + |
| 28 | +- **当前柱子小于前后两个柱子中最矮的那个** |
| 29 | +  |
| 30 | + |
| 31 | + **当前位置可以存储的雨水容量 = leftMax - curr = 1** |
| 32 | + |
| 33 | + |
| 34 | + |
| 35 | +- **当前柱子等于前后两个柱子中最矮的那个** |
| 36 | +  |
| 37 | + |
| 38 | + **当前位置可以存储的雨水容量为0** |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +- **当前柱子大于前后两个柱子中最矮的那个** |
| 43 | + |
| 44 | + **因为curr < leftMax,所以当前位置无法存储雨水** |
| 45 | + |
| 46 | +**GIF动画演示:** |
| 47 | + |
| 48 | + |
| 49 | + |
| 50 | +### 代码: |
| 51 | + |
| 52 | +```java |
| 53 | +public int trap02(int[] height) { |
| 54 | + int sum = 0; |
| 55 | + //最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2 |
| 56 | + for (int i = 1; i < height.length - 1; i++) { |
| 57 | + int max_left = 0; |
| 58 | + //找出左边最高 |
| 59 | + for (int j = i - 1; j >= 0; j--) { |
| 60 | + if (height[j] > max_left) { |
| 61 | + max_left = height[j]; |
| 62 | + } |
| 63 | + } |
| 64 | + int max_right = 0; |
| 65 | + //找出右边最高 |
| 66 | + for (int j = i + 1; j < height.length; j++) { |
| 67 | + if (height[j] > max_right) { |
| 68 | + max_right = height[j]; |
| 69 | + } |
| 70 | + } |
| 71 | + //找出两端较小的 |
| 72 | + int min = Math.min(max_left, max_right); |
| 73 | + //只有较小的一段大于当前列的高度才会有水,其他情况不会有水 |
| 74 | + if (min > height[i]) { |
| 75 | + sum = sum + (min - height[i]); |
| 76 | + } |
| 77 | + } |
| 78 | + return sum; |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +可以看到,上面方法的时间复杂度达到了**O(n^2)** |
| 83 | + |
| 84 | +**那么有没有更好的办法来解决这个问题?** |
| 85 | + |
| 86 | +下面的方法巧妙的使用了**双指针**来解决问题: |
| 87 | + |
| 88 | +与上述解法的思路大致是相同的,都是单个地求出当前墙可以存储雨水的容量;这种解法也是非常的巧妙,是在浏览解题区的时候碰见的,大佬还做了视频(链接放在文末),讲解的非常清楚,我大概用自己的思路来作一文字叙述: |
| 89 | + |
| 90 | +既然使用的是**twoPointers**的思路,那么我们需要分别从数组的最前面和最后面开始,这两个指针是互不影响,都是各走各的,但是如何确定当前指针走过的地方能存放多少雨水量呢? |
| 91 | + |
| 92 | +这个时候,我们就需要两块挡板**leftMax**和**rightMax**,这两块挡板最开始都是挡在最外面的墙边,随着两个指针前进,**leftMax**代表的是**left**走过的路中最高的墙,**rightMax**同理。 |
| 93 | + |
| 94 | +**那么如何计算雨水量呢?** |
| 95 | + |
| 96 | +比较左右两个挡板的高度,然后根据两个挡板各自的指针配合计算。 |
| 97 | + |
| 98 | +- 如果左边挡板的高度小于右边的挡板高度,那么左边指针之前的雨水量取决于**leftMax**和height[left]的大小关系,如果前者大于后者,那么容量等与前者减去后者;反之,容量为0(可以参考解法一中的图来理解) |
| 99 | +- 如果左边挡板的高度大于等于右边挡板的高度,与上一种情况基本相同,只不过是求的右边的雨水量。 |
| 100 | +- 在每次移动指针之后,我们要将挡板更新到最大值。 |
| 101 | + |
| 102 | +**其实道理也是比较简单,用宏观的思维去看待整个问题,最起码先保证两边的墙的高度(两块挡板),然后依次去到其中各个墙之间能装多少雨水的问题上。(求每次更新最高的挡板和指针指向的墙之间可以存储的雨水量)** |
| 103 | + |
| 104 | +### 代码: |
| 105 | + |
| 106 | +```java |
| 107 | +public int trap(int[] height) { |
| 108 | + if (height.length == 0) return 0; |
| 109 | + int left = 0; |
| 110 | + int right = height.length-1; |
| 111 | + int leftMax = 0; |
| 112 | + int rightMax = 0; |
| 113 | + int result = 0; |
| 114 | + while (left <= right) { |
| 115 | + if (leftMax < rightMax) { |
| 116 | + result += leftMax - height[left] > 0 ? |
| 117 | + leftMax - height[left] : 0; |
| 118 | + leftMax = Math.max(leftMax, height[left]); |
| 119 | + left++; |
| 120 | + } else { |
| 121 | + result += rightMax - height[right] > 0 ? |
| 122 | + rightMax - height[right] : 0; |
| 123 | + rightMax = Math.max(rightMax, height[right]); |
| 124 | + right--; |
| 125 | + } |
| 126 | + } |
| 127 | + return result; |
| 128 | + } |
| 129 | +``` |
| 130 | + |
| 131 | +**时间复杂度:O(n) 空间复杂度:O(1)** |
| 132 | + |
| 133 | +[leetcode配套视频入口](https://leetcode-cn.com/problems/trapping-rain-water/solution/javashi-pin-jiang-jie-xi-lie-trapping-rain-water-b/) |
| 134 | + |
0 commit comments