我的解法(和答案比很麻烦的双指针)
思路:从两边向中间进行两次扫描,分别处理左边和右边的蓄水情况
- 第一轮扫描(从左向右):
- 使用两个指针 slow 和 fast,slow 用来记录当前的最高柱子的位置,fast 用来探索后续柱子的高度。
- 如果
fast 位置的高度 height[fast] 小于 slow 位置的高度 height[slow]
,那么 fast 继续向右移动。 - 一旦
fast 找到了一个高度比 slow 高或相等的柱子,说明可以在 slow 和 fast 之间蓄水
。此时,遍历 slow 和 fast 之间的每一个位置,计算能够蓄水的量,蓄水量是 Math.min(height[fast], height[slow]) - height[i],其中 i 是 slow 和 fast 之间的位置。 - 然后,将 slow 更新为 fast,并让 fast 移动到 slow 的下一个位置,继续进行下一轮的探索。
- 第二轮扫描(从右向左):
- 如果第一轮扫描结束后,slow 没有移动到数组的最右端,意味着可能还存在从右向左方向可以蓄水的槽,因此需要进行第二轮扫描。
- 第二轮扫描与第一轮类似,只是从数组的右端开始,fast 指向数组的最右端,slow 指向 fast 左侧的柱子。
- 如果 slow 位置的高度 height[slow] 小于或等于 fast 位置的高度 height[fast],slow 左移,继续寻找能够蓄水的槽。
- 一旦找到了一个 slow 位置的高度大于 fast 的柱子,开始在 slow 和 fast 之间计算蓄水量,并更新 fast 和 slow 位置,继续进行下一轮的探索。
-
终止条件:
当 fast 和 slow 在第一轮或第二轮扫描中分别移动到数组的两端时,算法结束,返回最终的蓄水量 res。 -
关键思路总结:
- 双向扫描:代码通过两次扫描(从左向右、从右向左)来确保不会遗漏任何可能蓄水的槽。
- 局部最低点:每一轮扫描都会寻找局部的最低点,计算蓄水量。
- 重置指针:在每次找到一个新的可以形成蓄水槽的 fast 位置后,slow 和 fast 指针会重新定位,以继续寻找下一个蓄水槽。
class Solution {
public int trap(int[] height) {
int res = 0;
int slow = 0;
int fast = slow + 1;
while(fast < height.length){
if(height[fast] < height[slow]){
fast++;
}else{
for(int i = slow + 1; i < fast; i++){
res += Math.min(height[fast], height[slow]) - height[i];
}
slow = fast;
fast = slow + 1;
}
}
// 如果最后slow没有更新到右端,还需要从右向左再扫描一次
fast = height.length - 1;
slow = fast - 1;
while(slow >= 0){
if(height[slow] <= height[fast]){
slow--;
}else{
for(int i = slow + 1; i < fast; i++){
res += Math.min(height[fast], height[slow]) - height[i];
}
fast = slow;
slow = fast - 1;
}
}
return res;
}
}
题解一(双指针)
总体思路: 双指针法通过一次遍历数组,动态计算左右两侧的最高柱子高度,从而高效地计算出能够接住的雨水总量。
解题思路:
- 初始化双指针和最大值:
left 指针从数组的左端开始,初始值为 0。
right 指针从数组的右端开始,初始值为 height.length - 1。
leftMax 用来记录从左到右遍历过程中遇到的最高柱子,初始值为 0。
rightMax 用来记录从右到左遍历过程中遇到的最高柱子,初始值为 0。
ans 用来存储最后的结果,也就是能够接住的雨水的总量。 - 双指针移动和计算:
在每一步中,比较 height[left] 和 height[right] 的值:
- 如果
height[left] < height[right]
,说明左侧的柱子较低,这意味着可能在左侧积水。我们接下来看 left 指针的高度与 leftMax 的比较:- 更新 leftMax 为 leftMax 和 height[left] 中的较大值。这是为了确保 leftMax 始终是从左端到当前 left 位置的最高柱子高度。
- 计算当前
left 位置能积水的量,即 leftMax - height[left]
。如果 leftMax 比 height[left] 高,那么在 left 位置可以积水(两者的差值即为水的高度),将这个值加到 ans 中。 - left 指针右移,处理下一个位置。
- 如果
height[right] <= height[left]
,则说明右侧的柱子较低或相等,我们将使用类似的逻辑来处理 right 指针:- 更新 rightMax 为 rightMax 和 height[right] 中的较大值,确保 rightMax 始终是从右端到当前 right 位置的最高柱子高度。
- 计算当前
right 位置能积水的量,即 rightMax - height[right]
。同样,如果 rightMax 比 height[right] 高,积水量就为两者的差值,将其加到 ans 中。 - 将 right 指针左移,处理下一个位置。
核心思想总结:
- 动态维护左右两侧的最大高度:通过不断更新 leftMax 和 rightMax,确保始终有当前左右两侧最高的柱子高度,进而可以计算当前位置的积水量。
- 双指针法的优势:通过比较左右两侧的柱子高度,选择较低的一侧进行处理,确保能够正确地计算积水,且无需额外的空间来存储每个位置的最大高度。
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}
题解二(动态规划)
思路:
如果已经知道每个位置两边的最大高度,则可以在 O(n) 的时间内得到能接的雨水总量。使用动态规划的方法,可以在 O(n) 的时间内预处理得到每个位置两边的最大高度。
创建两个长度为 n 的数组 leftMax 和 rightMax。对于 0≤i<n,leftMax[i] 表示下标 i 及其左边的位置中,height 的最大高度,rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。
显然,leftMax[0]=height[0]
,rightMax[n−1]=height[n−1]
。两个数组的其余元素的计算如下:
-
当
1≤i≤n−1
时,leftMax[i]=max(leftMax[i−1],height[i]);
-
当
0≤i≤n−2
时,rightMax[i]=max(rightMax[i+1],height[i]);
因此可以正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。
在得到数组 leftMax 和 rightMax 的每个元素值之后,对于 0≤i<n,下标 i 处能接的雨水量等于 min(leftMax[i],rightMax[i])−height[i]
。遍历每个下标位置即可得到能接的雨水总量。
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) {
return 0;
}
int[] leftMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
int[] rightMax = new int[n];
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}