176 lines
6.2 KiB
Java
Raw Normal View History

2025-04-02 18:28:11 +08:00
package stack;
import java.util.Deque;
import java.util.LinkedList;
/**
* 题目 42. 接雨水 (trap)
* 描述给定 n 个非负整数表示每个宽度为 1 的柱子的高度图计算按此排列的柱子下雨之后能接多少雨水
* 示例 1
* 输入height = [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 个单位的雨水蓝色部分表示雨水
*
* 链接https://leetcode.cn/problems/trapping-rain-water/
*/
//困难题 不会
2025-06-11 10:48:25 +08:00
//二刷不会
2025-04-02 18:28:11 +08:00
public class Trap {
2025-06-11 10:48:25 +08:00
//暴力解法 竖着收集雨水
2025-04-02 18:28:11 +08:00
public int trap1(int[] height) {
int sum = 0;
for (int i = 0; i < height.length; i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.length - 1) {
continue;
}
2025-06-11 10:48:25 +08:00
/**
* 目的对于每一个柱子 i想知道它左右两边的最高柱子分别是多少
*
* 因为要接雨水关键在于当前位置能接多少水取决于
*
* 它左边的最高柱子
* 它右边的最高柱子
* 当前柱子的高度
*
*
*/
int rHeight = height[i]; // 初始化右边最大高度为当前位置高度(后面会尝试找更高的)
int lHeight = height[i]; // 初始化左边最大高度为当前位置高度(后面会尝试找更高的)
2025-04-02 18:28:11 +08:00
2025-06-11 10:48:25 +08:00
// 从当前位置 i 向右扫描,找出右侧最高的柱子高度
2025-04-02 18:28:11 +08:00
for (int r = i + 1; r < height.length; r++) {
if (height[r] > rHeight) {
2025-06-11 10:48:25 +08:00
rHeight = height[r]; // 更新右侧最高高度
2025-04-02 18:28:11 +08:00
}
}
2025-06-11 10:48:25 +08:00
// 从当前位置 i 向左扫描,找出左侧最高的柱子高度
2025-04-02 18:28:11 +08:00
for (int l = i - 1; l >= 0; l--) {
if (height[l] > lHeight) {
2025-06-11 10:48:25 +08:00
lHeight = height[l]; // 更新左侧最高高度
2025-04-02 18:28:11 +08:00
}
}
2025-06-11 10:48:25 +08:00
2025-04-02 18:28:11 +08:00
int h = Math.min(lHeight, rHeight) - height[i];
if (h > 0) {
sum += h;
}
}
return sum;
}
2025-06-11 10:48:25 +08:00
//动态规划 竖着收集雨水
/**
* 对于每一个柱子 i接雨水的能力取决于它左右两边最高柱子的最小值
* 因此预先构造两个数组
* maxLeft[i] 表示从左到 i 的最大值
* maxRight[i] 表示从右到 i 的最大值
* 然后遍历每一个柱子除了两端计算
* Math.min(maxLeft[i - 1], maxRight[i + 1]) - height[i]
*
* @param height
* @return
*/
2025-04-02 18:28:11 +08:00
public int trap2(int[] height) {
if (height.length <= 2) return 0;
int n = height.length;
int[] maxLeft = new int[n];
int[] maxRight = new int[n];
// 记录每个柱子左边柱子最大高度
maxLeft[0] = height[0];
for (int i = 1; i < n; i++) {
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}
// 记录每个柱子右边柱子最大高度
maxRight[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}
// 求和
int sum = 0;
for (int i = 1; i < n-1; i++) {
int count = Math.min(maxLeft[i-1], maxRight[i+1]) - height[i];
if (count > 0) {
sum += count;
}
}
return sum;
}
2025-06-11 10:48:25 +08:00
//双指针 竖着收集雨水
/**
* 左右各有一个指针从两端向中间走
* leftMax rightMax 分别记录从左右遇到的最高柱子
* 每次比较 height[left] height[right]
* 谁矮就处理谁因为能接水的高度取决于当前方向上历史最高柱子
* 用双指针减少了空间整体时间仍然是 O(n)
* @param height
* @return
*/
public int trap3(int[] height) {
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
int sum = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] > leftMax) {
leftMax = height[left];
} else {
sum += leftMax - height[left];
}
left++;
} else {
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
sum += rightMax - height[right];
}
right--;
}
}
return sum;
}
//单调栈 横着收集雨水
/**
* 使用一个单调递减栈存的是索引遇到比栈顶高的柱子就说明可以接水了
* 每当当前柱子比栈顶柱子高就弹出栈顶作为凹槽底部
* 然后新的栈顶变成左边界当前柱子是右边界
* 这三者一起决定了可以接的水量
* distance = i - stack.peek() - 1
* height = min(height[i], height[stack.peek()]) - height[top]
*
* 遇到不能接水的就把当前柱子索引压入栈中等待后面的柱子来形成边界
* @param height
* @return
*/
2025-04-02 18:28:11 +08:00
public int trap(int[] height) {
int sum = 0;
Deque<Integer> stack = new LinkedList<>();
for (int i = 0; i < height.length; i++) {
// 当栈不为空且当前柱子的高度大于栈顶柱子的高度时
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int top = stack.pop();
// 如果栈为空,则没有左边的柱子来接雨水
if (stack.isEmpty()) break;
// 计算当前柱子与栈顶柱子之间的距离
int distance = i - stack.peek() - 1;
// 计算可以接的雨水高度:两边较低的高度减去中间柱子的高度
int boundedHeight = Math.min(height[i], height[stack.peek()]) - height[top];
sum += distance * boundedHeight;
}
// 将当前索引入栈
stack.push(i);
}
return sum;
}
}