42. 接雨水
难度困难2179
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
1 | 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] |
示例 2:
1 | 输入:height = [4,2,0,3,2,5] |
提示:
n == height.length
0 <= n <= 3 * 10^4
0 <= height[i] <= 10^5
暴力求解
1 | public int trap(int[] height) { |
动态规划
1 | public int trap(int[] height) { |
单调栈
单调栈的维护是 O(n) 级的时间复杂度,因为所有元素只会进入栈一次,并且出栈后再也不会进栈了。
单调栈的性质:
- 单调栈里的元素具有单调性;
- 元素加入栈前,会在栈顶端把破坏栈单调性的元素都删除;
- 使用单调栈可以找到元素向左遍历第一个比他小的元素,也可以找到元素向左遍历第一个比他大的元素。
1 | public int trap(int[] height) { |
双指针
双指针法真的妙,那么如何理解双指针法呢?听我来给你捋一捋。(捋的过程和原解中的C++在细节方面的处理是有出入的)
我们先明确几个变量的意思:
- left_max:左边的最大值,它是从左往右遍历找到的
- right_max:右边的最大值,它是从右往左遍历找到的
- left:从左往右处理的当前下标
- right:从右往左处理的当前下标
定理一:在某个位置i处,它能存的水,取决于它左右两边的最大值中较小的一个。
定理二:当我们从左往右处理到left下标时,左边的最大值left_max对它而言是可信的,但right_max对它而言是不可信的。(见下图,由于中间状况未知,对于left下标而言,right_max未必就是它右边最大的值)
定理三:当我们从右往左处理到right下标时,右边的最大值right_max对它而言是可信的,但left_max对它而言是不可信的。
1 | right_max |
对于位置left而言,它左边最大值一定是left_max,右边最大值“大于等于”right_max,这时候,如果left_max<right_max成立,那么它就知道自己能存多少水了。无论右边将来会不会出现更大的right_max,都不影响这个结果。 所以当left_max<right_max时,我们就希望去处理left下标,反之,我们希望去处理right下标。
上述内容引用自:
Lucien的评论
只要左边目前的最大值小于右边目前的最大值,那么当前位置盛水的能力就由左边的最大值决定,直接处理左边的部分,否则目前盛水的最大值由右边决定,需要从右边开始处理。
如果右边目前的最大值小于左边目前的最大值,那么当前位置盛水的能力就由右边的最大值决定,直接处理右边的部分,否则目前盛水的最大值由左边决定,需要从左边开始处理。
1 | class Solution { |
1 | class Solution { |