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