42. 接雨水 (Trapping Rain Water)
题目来源
题目分析
在这道题目中,我们需要计算给定高度图中能接住多少雨水。题目描述如下:
题目: 给定
n
个非负整数表示每个宽度为1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
题目难度
- 难度:困难
题目标签
- 标签:数组、双指针、动态规划、栈
题目限制
n == height.length
0 <= n <= 2 * 10^4
0 <= height[i] <= 10^5
解题思路
这道题的核心思路是找到每个柱子能容纳多少雨水。容纳的水量取决于其左右两边柱子的最大高度。每个柱子容纳的水量=左和右两边柱子最大高度中的最小值(木桶原理)- 每个柱子的高度
方法一:前后缀分解
首先,我们可以通过计算每个柱子左边的最大高度和右边的最大高度来确定当前柱子所能容纳的水量。具体步骤如下:
- 计算前缀最大值:
pre_max[i]
记录i
之前(包括i
)的最大值。 - 计算后缀最大值:
suf_max[i]
记录i
之后(包括i
)的最大值。 - 计算水量:对于每个柱子,可以接的水量为
min(pre_max[i], suf_max[i]) - height[i]
,并累加得到总水量。
方法二:双指针
双指针方法是对前后缀分解方法的优化。我们不需要单独记录前缀和后缀的最大值,而是通过两个指针从数组的两端向中间移动,动态更新当前左右两边的最大高度。记录前缀最大值pre_max
和后缀最大值suf_max
。
因为对于每个柱子来说,是要找前缀最大值pre_max
和后缀最大值suf_max
中的较小值,所以当当前的前缀最大值pre_max
小于后缀最大值suf_max
时,我们已经可以确定left指针指向的柱子的min(pre_max[i], suf_max[i])
可以计算水量了,同理,当前缀最大值pre_max
大于后缀最大值suf_max
时,我们可以确定right指针指向的柱子的min(pre_max[i], suf_max[i])
- 初始化指针:
left
从左边开始,right
从右边开始。 - 更新最大值:分别更新
left
和right
所在位置的最大高度。 - 计算水量:如果左边最大值小于右边最大值,则计算左边柱子的水量,并将
left
向右移动;否则计算右边柱子的水量,并将right
向左移动。 - 返回结果:最终累加所有柱子上的水量。
代码实现
方法一:前后缀分解
public int trap(int[] height) {
int n = height.length;
int ans = 0;
int[] pre_max = new int[n];
int[] suf_max = new int[n];
pre_max[0] = height[0];
suf_max[n - 1] = height[n - 1];
for (int i = 1; i < n; i++) {
pre_max[i] = Math.max(pre_max[i - 1], height[i]);
}
for (int i = n - 2; i >= 0; i--) {
suf_max[i] = Math.max(suf_max[i + 1], height[i]);
}
for (int i = 0; i < n; i++) {
ans += Math.min(pre_max[i], suf_max[i]) - height[i];
}
return ans;
}
方法二:双指针
public int trap2(int[] height) {
int n = height.length;
int ans = 0;
int left = 0, right = n - 1;
int pre_max = 0, suf_max = 0;
while (left <= right) {
pre_max = Math.max(pre_max, height[left]);
suf_max = Math.max(suf_max, height[right]);
if (pre_max < suf_max) {
ans += pre_max - height[left];
left++;
} else {
ans += suf_max - height[right];
right--;
}
}
return ans;
}
代码解读
- 前后缀分解方法:时间复杂度为
O(n)
,空间复杂度为O(n)
。先分别计算每个柱子的左右最大高度,然后计算每个柱子能容纳的水量。 - 双指针方法:时间复杂度为
O(n)
,空间复杂度为O(1)
。通过双指针动态更新左右最大高度,并计算水量。
测试用例
以下是几个测试用例,用于验证代码的正确性:
public static void main(String[] args) {
int[] height1 = {0,1,0,2,1,0,1,3,2,1,2,1};
int[] height2 = {4,2,0,3,2,5};
System.out.println(trap(height1)); // 输出: 6
System.out.println(trap2(height2)); // 输出: 9
}
性能分析
- 时间复杂度:
O(n)
,其中n
是高度数组的长度。 - 空间复杂度:
- 前后缀分解方法:
O(n)
。 - 双指针方法:
O(1)
。
- 前后缀分解方法:
总结
本题通过计算柱子的左右最大高度来确定能接多少雨水。前后缀分解方法较为直观,而双指针方法在空间复杂度上更具优势。选择合适的解法可以更高效地解决问题。