176 lines
6.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}