diff --git a/interview/Trapping_Rain_Water.md b/interview/Trapping_Rain_Water.md new file mode 100644 index 0000000000..d4be0c1332 --- /dev/null +++ b/interview/Trapping_Rain_Water.md @@ -0,0 +1,185 @@ +# Detailed analysis of the trapping rain water problem + +**Translator: [Iruze](https://github.com/Iruze)** + +**Author: [labuladong](https://github.com/labuladong)** + +The trapping rain water problem is very interesting and preforms frequently in interviews. So this paper will show how to solve the problem and explain how to optimize the solution step by step. + +First of all, let's have a view on the problem: + +![](../pictures/trapping_rain_water/title.jpg) + +In a word, an array represents an elevation map and hope you calculate how much rain water the elevation map can hold at most. + +```java +int trap(int[] height); +``` + +Now I will explain three approaches from shallow to deep: Brute force -> Using memorandum -> Using two pointers, and finally solve the problem with O(1) space complexity and O(N) time complexity. + +### I. Core idea + +When I saw this problem for the first time, I had no idea at all. I believe that many friends have the same experience. As for this kind of problem, we should not consider from the whole, but from the part; Just as the previous articles that talk about how to handle the string problem, don't consider how to handle the whole string. Instead, you should focus on how to handle each character among the string. + +Therefore, we find that the thought of this problem is sample. Specifically, just for the position `i` as below, how much water can it hold? + +![](../pictures/trapping_rain_water/0.jpg) + +Position `i` occupies 2 grids for holding water. Why it happens to hold 2 grids of water? Because the height of `height[i]` is 0, and `height[i]` can hold up to 2 grids of water, therefore there exists 2 - 0 = 2. + +But why the position `i` can hold 2 grids of water at most? Because the height of water column at position `i` depends on both the hightest water column on the left and the highest water column on the right. We describe the height of the two highest water columns as `l_max` and `r_max` respectively. **Thus the height at position `i` is `min(l_max, r_max)`**. + +Further more, as for the position `i`, how much water it holds can be demonstrated as: +```python +water[i] = min( + # the highest column on the left + max(height[0..i]), + # the highest column on the right + max(height[i..end]) + ) - height[i] +``` + +![](../pictures/trapping_rain_water/1.jpg) + +![](../pictures/trapping_rain_water/2.jpg) + +This is the core idea of the problem, so we can program a simple brute approach: + +```cpp +int trap(vector& height) { + int n = height.size(); + int ans = 0; + for (int i = 1; i < n - 1; i++) { + int l_max = 0, r_max = 0; + // find the highest column on the right + for (int j = i; j < n; j++) + r_max = max(r_max, height[j]); + // find the highest column on the right + for (int j = i; j >= 0; j--) + l_max = max(l_max, height[j]); + // if the position i itself is the highest column + // l_max == r_max == height[i] + ans += min(l_max, r_max) - height[i]; + } + return ans; +} +``` + +According to the previous thought, the above approach seems very direct and brute. The time complexity is O(N^2) and the space complexity is O(1). However, it is obvious that the way of calculating `r_max` and `l_max` is very clumsy, which the memorandum is generally introduced to optimize the way. + +### II. Memorandum Optimization + +In the previous brute approach, the `r_max` and `l_max` are calculated at every position `i`. So we can cache that calculation results, which avoids the stupid traversal at every time. Thus the time complexity will reasonably decline. + +Here two arrays `r_max` and `l_max` are used to act the memo. `l_max[i]` represents the highest column on the left of position `i` and `r_max[i]` represents the highest column on the right of position `i`. These two arrays are calculated in advance to avoid duplicated calculation. + +```cpp +int trap(vector& height) { + if (height.empty()) return 0; + int n = height.size(); + int ans = 0; + // arrays act the memo + vector l_max(n), r_max(n); + // initialize base case + l_max[0] = height[0]; + r_max[n - 1] = height[n - 1]; + // calculate l_max from left to right + for (int i = 1; i < n; i++) + l_max[i] = max(height[i], l_max[i - 1]); + // calculate r_max from right to left + for (int i = n - 2; i >= 0; i--) + r_max[i] = max(height[i], r_max[i + 1]); + // calculate the final result + for (int i = 1; i < n - 1; i++) + ans += min(l_max[i], r_max[i]) - height[i]; + return ans; +} +``` + +Actually, the memo optimization has not much difference from the above brute approach, except that it avoids repeat calculation and reduces the time complexity to O(N). Although time complexity O(N) is already the best, but the space complexity is still O(N). So let's look at a more subtle approach that can reduce the space complexity to O(1). + +### III. Two pointers + +The thought of this approach is exactly the same, but it is very ingenious in the way of implementation. We won't use the memo to cache calculation results in advance this time. Instead, we use two pointers to calculate during traversal and the space complexity will decline as a result. + +First, look at some of the code: + +```cpp +int trap(vector& height) { + int n = height.size(); + int left = 0, right = n - 1; + + int l_max = height[0]; + int r_max = height[n - 1]; + + while (left <= right) { + l_max = max(l_max, height[left]); + r_max = max(r_max, height[right]); + left++; right--; + } +} +``` + +In the above code, what's the meaning of `l_max` and `r_max` respectively? + +It is easy to understand that **`l_max` represents the highest column among `height[0..left]` and `r_max` represents the highest column among `height[right..end]`**. + +With that in mind, look directly at the approach: + +```cpp +int trap(vector& height) { + if (height.empty()) return 0; + int n = height.size(); + int left = 0, right = n - 1; + int ans = 0; + + int l_max = height[0]; + int r_max = height[n - 1]; + + while (left <= right) { + l_max = max(l_max, height[left]); + r_max = max(r_max, height[right]); + + // ans += min(l_max, r_max) - height[i] + if (l_max < r_max) { + ans += l_max - height[left]; + left++; + } else { + ans += r_max - height[right]; + right--; + } + } + return ans; +} +``` + +The core idea of the approach is the same as before, which is just like old wine in new bottle. However, a careful reader may find that the approach is slightly different in details from the previous ones: + +In the memo optimization approach, `l_max[i]` and `r_max[i]` represent the highest column of `height[0..i]` and `height[i..end]` respectively. + +```cpp +ans += min(l_max[i], r_max[i]) - height[i]; +``` + +![](../pictures/trapping_rain_water/3.jpg) + +But in two pointers approach, `l_max` and `r_max` represent the highest column of `height[0..left]` and `height[right..end]` respectively. Take the below code as an example: + +```cpp +if (l_max < r_max) { + ans += l_max - height[left]; + left++; +} +``` + +![](../pictures/trapping_rain_water/4.jpg) + +At this time, `l_max` represents the highest column on the left of `left` pointer, but `r_max` is not always the highest column on the right of `left` pointer. Under the circumstances, can this approach really get the right answer? + +In fact, we need to think about it in this way: we just focus on `min(l_max, r_max)`. In the above elevation map, we have known `l_max < r_max`, so it is doesn't matter whether the `r_max` is the highest column on the right. The key is that water capacity in `height[i]` just depends on `l_max`. + +![](../pictures/trapping_rain_water/5.jpg) + +***Tip:*** +Adhere to the original high-quality articles and strive to make the algorithm clear. Welcome to my Wechat official account: **labuladong** to get the latest articles. diff --git "a/interview/\346\216\245\351\233\250\346\260\264.md" "b/interview/\346\216\245\351\233\250\346\260\264.md" deleted file mode 100644 index f378322351..0000000000 --- "a/interview/\346\216\245\351\233\250\346\260\264.md" +++ /dev/null @@ -1,184 +0,0 @@ -# 接雨水问题详解 - -接雨水这道题目挺有意思,在面试题中出现频率还挺高的,本文就来步步优化,讲解一下这道题。 - -先看一下题目: - -![](../pictures/接雨水/title.png) - -就是用一个数组表示一个条形图,问你这个条形图最多能接多少水。 - -```java -int trap(int[] height); -``` - -下面就来由浅入深介绍暴力解法 -> 备忘录解法 -> 双指针解法,在 O(N) 时间 O(1) 空间内解决这个问题。 - -### 一、核心思路 - -我第一次看到这个问题,无计可施,完全没有思路,相信很多朋友跟我一样。所以对于这种问题,我们不要想整体,而应该去想局部;就像之前的文章处理字符串问题,不要考虑如何处理整个字符串,而是去思考应该如何处理每一个字符。 - -这么一想,可以发现这道题的思路其实很简单。具体来说,仅仅对于位置 i,能装下多少水呢? - -![](../pictures/接雨水/0.jpg) - -能装 2 格水。为什么恰好是两格水呢?因为 height[i] 的高度为 0,而这里最多能盛 2 格水,2-0=2。 - -为什么位置 i 最多能盛 2 格水呢?因为,位置 i 能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关,我们分别称这两个柱子高度为 `l_max` 和 `r_max`;**位置 i 最大的水柱高度就是 `min(l_max, r_max)`。** - -更进一步,对于位置 i,能够装的水为: - -```python -water[i] = min( - # 左边最高的柱子 - max(height[0..i]), - # 右边最高的柱子 - max(height[i..end]) - ) - height[i] - -``` - -![](../pictures/%E6%8E%A5%E9%9B%A8%E6%B0%B4/1.jpg) - -![](../pictures/%E6%8E%A5%E9%9B%A8%E6%B0%B4/2.jpg) - -这就是本问题的核心思路,我们可以简单写一个暴力算法: - -```cpp -int trap(vector& height) { - int n = height.size(); - int ans = 0; - for (int i = 1; i < n - 1; i++) { - int l_max = 0, r_max = 0; - // 找右边最高的柱子 - for (int j = i; j < n; j++) - r_max = max(r_max, height[j]); - // 找左边最高的柱子 - for (int j = i; j >= 0; j--) - l_max = max(l_max, height[j]); - // 如果自己就是最高的话, - // l_max == r_max == height[i] - ans += min(l_max, r_max) - height[i]; - } - return ans; -} -``` - -有之前的思路,这个解法应该是很直接粗暴的,时间复杂度 O(N^2),空间复杂度 O(1)。但是很明显这种计算 `r_max` 和 `l_max` 的方式非常笨拙,一般的优化方法就是备忘录。 - -### 二、备忘录优化 - -之前的暴力解法,不是在每个位置 i 都要计算 `r_max` 和 `l_max` 吗?我们直接把结果都缓存下来,别傻不拉几的每次都遍历,这时间复杂度不就降下来了嘛。 - -我们开两个**数组** `r_max` 和 `l_max` 充当备忘录,`l_max[i]` 表示位置 i 左边最高的柱子高度,`r_max[i]` 表示位置 i 右边最高的柱子高度。预先把这两个数组计算好,避免重复计算: - -```cpp -int trap(vector& height) { - if (height.empty()) return 0; - int n = height.size(); - int ans = 0; - // 数组充当备忘录 - vector l_max(n), r_max(n); - // 初始化 base case - l_max[0] = height[0]; - r_max[n - 1] = height[n - 1]; - // 从左向右计算 l_max - for (int i = 1; i < n; i++) - l_max[i] = max(height[i], l_max[i - 1]); - // 从右向左计算 r_max - for (int i = n - 2; i >= 0; i--) - r_max[i] = max(height[i], r_max[i + 1]); - // 计算答案 - for (int i = 1; i < n - 1; i++) - ans += min(l_max[i], r_max[i]) - height[i]; - return ans; -} -``` - -这个优化其实和暴力解法差不多,就是避免了重复计算,把时间复杂度降低为 O(N),已经是最优了,但是空间复杂度是 O(N)。下面来看一个精妙一些的解法,能够把空间复杂度降低到 O(1)。 - -### 三、双指针解法 - -这种解法的思路是完全相同的,但在实现手法上非常巧妙,我们这次也不要用备忘录提前计算了,而是用双指针**边走边算**,节省下空间复杂度。 - -首先,看一部分代码: - -```cpp -int trap(vector& height) { - int n = height.size(); - int left = 0, right = n - 1; - - int l_max = height[0]; - int r_max = height[n - 1]; - - while (left <= right) { - l_max = max(l_max, height[left]); - r_max = max(r_max, height[right]); - left++; right--; - } -} -``` - -对于这部分代码,请问 `l_max` 和 `r_max` 分别表示什么意义呢? - -很容易理解,**`l_max` 是 `height[0..left]` 中最高柱子的高度,`r_max` 是 `height[right..end]` 的最高柱子的高度**。 - -明白了这一点,直接看解法: - -```cpp -int trap(vector& height) { - if (height.empty()) return 0; - int n = height.size(); - int left = 0, right = n - 1; - int ans = 0; - - int l_max = height[0]; - int r_max = height[n - 1]; - - while (left <= right) { - l_max = max(l_max, height[left]); - r_max = max(r_max, height[right]); - - // ans += min(l_max, r_max) - height[i] - if (l_max < r_max) { - ans += l_max - height[left]; - left++; - } else { - ans += r_max - height[right]; - right--; - } - } - return ans; -} -``` - -你看,其中的核心思想和之前一模一样,换汤不换药。但是细心的读者可能会发现次解法还是有点细节差异: - -之前的备忘录解法,`l_max[i]` 和 `r_max[i]` 代表的是 `height[0..i]` 和 `height[i..end]` 的最高柱子高度。 - -```cpp -ans += min(l_max[i], r_max[i]) - height[i]; -``` - -![](../pictures/%E6%8E%A5%E9%9B%A8%E6%B0%B4/3.jpg) - -但是双指针解法中,`l_max` 和 `r_max` 代表的是 `height[0..left]` 和 `height[right..end]` 的最高柱子高度。比如这段代码: - -```cpp -if (l_max < r_max) { - ans += l_max - height[left]; - left++; -} -``` - -![](../pictures/%E6%8E%A5%E9%9B%A8%E6%B0%B4/4.jpg) - -此时的 `l_max` 是 `left` 指针左边的最高柱子,但是 `r_max` 并不一定是 `left` 指针右边最高的柱子,这真的可以得到正确答案吗? - -其实这个问题要这么思考,我们只在乎 `min(l_max, r_max)`。对于上图的情况,我们已经知道 `l_max < r_max` 了,至于这个 `r_max` 是不是右边最大的,不重要,重要的是 `height[i]` 能够装的水只和 `l_max` 有关。 - -![](../pictures/%E6%8E%A5%E9%9B%A8%E6%B0%B4/5.jpg) - -坚持原创高质量文章,致力于把算法问题讲清楚,欢迎关注我的公众号 labuladong 获取最新文章: - -![labuladong](../pictures/labuladong.jpg) diff --git a/pictures/trapping_rain_water/0.jpg b/pictures/trapping_rain_water/0.jpg new file mode 100644 index 0000000000..2d2e1cb645 Binary files /dev/null and b/pictures/trapping_rain_water/0.jpg differ diff --git a/pictures/trapping_rain_water/1.jpg b/pictures/trapping_rain_water/1.jpg new file mode 100644 index 0000000000..842c5582d3 Binary files /dev/null and b/pictures/trapping_rain_water/1.jpg differ diff --git a/pictures/trapping_rain_water/2.jpg b/pictures/trapping_rain_water/2.jpg new file mode 100644 index 0000000000..7fc3635c47 Binary files /dev/null and b/pictures/trapping_rain_water/2.jpg differ diff --git a/pictures/trapping_rain_water/3.jpg b/pictures/trapping_rain_water/3.jpg new file mode 100644 index 0000000000..b9403de6e5 Binary files /dev/null and b/pictures/trapping_rain_water/3.jpg differ diff --git a/pictures/trapping_rain_water/4.jpg b/pictures/trapping_rain_water/4.jpg new file mode 100644 index 0000000000..9906e07537 Binary files /dev/null and b/pictures/trapping_rain_water/4.jpg differ diff --git a/pictures/trapping_rain_water/5.jpg b/pictures/trapping_rain_water/5.jpg new file mode 100644 index 0000000000..3a6ffb56b2 Binary files /dev/null and b/pictures/trapping_rain_water/5.jpg differ diff --git a/pictures/trapping_rain_water/title.jpg b/pictures/trapping_rain_water/title.jpg new file mode 100644 index 0000000000..7268c37352 Binary files /dev/null and b/pictures/trapping_rain_water/title.jpg differ