42. 接雨水 【困难题】【双指针】
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
题目讲解
解法一【暴力解】
【核心思想】
每个格子当前能装的水量,都与其两边最高的柱子有关。设其左边最高的柱子为leftMax
,其右边最高的柱子为rightMax
,则当前格子i
能装的水量为min(leftMax,rightMax)-height[i]
。这个基本思路可以由暴力搜索解决,复杂度为
O
(
n
2
)
O(n^2)
O(n2)
【核心代码】
for (int i = 1; i < n - 1; i++) {
int leftMax = 0, rightMax = 0;
// 找右边最高的柱子
for (int j = i; j < n; j++)
rightMax = max(rightMax, height[j]);
// 找左边最高的柱子
for (int j = i; j >= 0; j--)
leftMax = max(leftMax, height[j]);
ans += min(leftMax, rightMax) - height[i];
}
解法二【暴力解的改进】
【核心思想】
上一个暴力解法,每个位置 i
都要计算rightMax
和leftMax
,现在我们直接把结果都缓存下来。
我们开两个数组rightMax[]
和leftMax[]
,leftMax[i]
表示位置i
左边最高的柱子高度,rightMax[i]
表示位置i
右边最高的柱子高度。预先把这两个数组计算好,避免重复计算。时间复杂度为
O
(
n
)
O(n)
O(n),空间复杂度为
O
(
n
)
O(n)
O(n)
【核心代码】
// 初始化
leftMax[0] = height[0];
rightMax[n - 1] = height[n - 1];
// 从左向右计算 leftMax
for (int i = 1; i < n; i++)
leftMax[i] = max(height[i], leftMax[i - 1]);
// 从右向左计算 rightMax
for (int i = n - 2; i >= 0; i--)
rightMax[i] = max(height[i], rightMax[i + 1]);
// 计算答案
for (int i = 1; i < n - 1; i++)
ans += min(leftMax[i], rightMax[i]) - height[i];
return ans;
解法三【双指针】
【核心思想】
这次也不要用数组提前计算了,而是用双指针边走边算,节省下空间复杂度。
【思路】
- 设置两个指针,一个放在数组开始,一个放在数组末尾
- 设置两个值存放当前位置
i
左边最高的柱子高度leftMax
和右边最高的柱子高度rightMax
- 当遍历到当前位置
i
时,计算min(leftMax,rightMax)-height[i]
,这就是当前位置能装的雨水 - 时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)
【完整代码】
public int trap(int[] height) {
int left=0,right=height.length-1;
int leftMax=0,rightMax=0;
int ans=0;
while(left<=right){
leftMax=Math.max(leftMax,height[left]);
rightMax=Math.max(rightMax,height[right]);
if (leftMax < rightMax) {
ans += leftMax - height[left];
left++;
}
else {
ans += rightMax - height[right];
right--;
}
}
return ans;
}
关注微信公众号“算法岗从零到无穷”,更多算法知识点告诉你。