176 lines
6.2 KiB
Java
176 lines
6.2 KiB
Java
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/
|
||
*/
|
||
//困难题 不会
|
||
//二刷不会
|
||
public class Trap {
|
||
//暴力解法 竖着收集雨水
|
||
public int trap1(int[] height) {
|
||
int sum = 0;
|
||
for (int i = 0; i < height.length; i++) {
|
||
// 第一个柱子和最后一个柱子不接雨水
|
||
if (i == 0 || i == height.length - 1) {
|
||
continue;
|
||
}
|
||
/**
|
||
* 目的:对于每一个柱子 i,想知道它“左右两边的最高柱子”分别是多少。
|
||
*
|
||
* 因为要接雨水,关键在于“当前位置能接多少水”取决于:
|
||
*
|
||
* 它左边的最高柱子
|
||
* 它右边的最高柱子
|
||
* 当前柱子的高度
|
||
*
|
||
*
|
||
*/
|
||
int rHeight = height[i]; // 初始化右边最大高度为当前位置高度(后面会尝试找更高的)
|
||
int lHeight = height[i]; // 初始化左边最大高度为当前位置高度(后面会尝试找更高的)
|
||
|
||
// 从当前位置 i 向右扫描,找出右侧最高的柱子高度
|
||
for (int r = i + 1; r < height.length; r++) {
|
||
if (height[r] > rHeight) {
|
||
rHeight = height[r]; // 更新右侧最高高度
|
||
}
|
||
}
|
||
|
||
// 从当前位置 i 向左扫描,找出左侧最高的柱子高度
|
||
for (int l = i - 1; l >= 0; l--) {
|
||
if (height[l] > lHeight) {
|
||
lHeight = height[l]; // 更新左侧最高高度
|
||
}
|
||
}
|
||
|
||
|
||
int h = Math.min(lHeight, rHeight) - height[i];
|
||
if (h > 0) {
|
||
sum += h;
|
||
}
|
||
}
|
||
return sum;
|
||
}
|
||
//动态规划 竖着收集雨水
|
||
|
||
/**
|
||
* 对于每一个柱子 i,接雨水的能力取决于它左右两边最高柱子的最小值。
|
||
* 因此,预先构造两个数组:
|
||
* maxLeft[i] 表示从左到 i 的最大值
|
||
* maxRight[i] 表示从右到 i 的最大值
|
||
* 然后遍历每一个柱子(除了两端),计算:
|
||
* Math.min(maxLeft[i - 1], maxRight[i + 1]) - height[i]
|
||
*
|
||
* @param height
|
||
* @return
|
||
*/
|
||
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;
|
||
}
|
||
|
||
//双指针 竖着收集雨水
|
||
/**
|
||
* 左右各有一个指针,从两端向中间走
|
||
* 用 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
|
||
*/
|
||
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;
|
||
}
|
||
}
|