LeetCode——面试题 17.21. 直方图的水量[Volume of Histogram LCCI][困难]——分析及代码[Java]
一、题目
给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 1。
由数组 [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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/volume-of-histogram-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二、分析及代码
1. 逐层计算
(1)思路
自底向上逐层计算,在遍历过程中记录达到当前高度的左边界,根据右边界计算当前区域存水量。
(2)代码
class Solution {
public int trap(int[] height) {
int ans = 0, n = height.length;
boolean hasWater = true;//当前高度是否有水
int h = 0;//当前高度
while (hasWater == true) {
hasWater = false;
int l = -1;//左边界
h++;
for (int i = 0; i < n; i++) {
if (height[i] >= h) {
if (l > 0) {
ans += i - l;//该区域的水
hasWater = true;
}
l = i + 1;//更新左边界
}
}
}
return ans;
}
}
(3)结果
执行用时 :968 ms,在所有 Java 提交中击败了 5.01% 的用户;
内存消耗 :38.1 MB,在所有 Java 提交中击败了 55.25% 的用户。
2. 单调栈
(1)思路
若直方图中长方形的最高高度为 h1,次高高度为 h2,则对其区间内的每个位置 i,存水量为 h2 - height[i]。
结合这一思路,可设计 2 个单调栈,分别存储从左向右、从右向左长方形高度升序对应的位置。再依次向左、右取出最高高度、次高高度,并计算区间内的存水量。
(2)代码
class Solution {
public int trap(int[] height) {
int n = height.length, ans = 0;
if (n == 0)
return 0;
//单调栈,存储从左到右、从右到左长方形高度升序对应的位置
Deque<Integer> staLeft = new LinkedList<>();
Deque<Integer> staRight = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (height[i] > 0 && (staLeft.isEmpty() || height[staLeft.peekLast()] <= height[i]))
staLeft.offer(i);
}
for (int i = n - 1; i >= 0; i--) {
if (height[i] > 0 && (staRight.isEmpty() || height[staRight.peekLast()] <= height[i]))
staRight.offer(i);
}
if (staLeft.isEmpty())
return 0;
//从最高的长方形向左,依次寻找次高长方形并计算区间内存水量
int h1 = 0, h2 = staLeft.pollLast(), maxHeight = h2;
while (!staLeft.isEmpty()) {
h1 = h2;
h2 = staLeft.pollLast();
for (int i = h2 + 1; i < h1; i++)
ans += height[h2] - height[i];
}
//从最高的长方形向右,依次寻找次高长方形并计算区间内存水量
while (!staRight.isEmpty() && height[staRight.peekLast()] == height[maxHeight])//排除以最高的长方形为两边边界,导致左右重复的部分
h2 = staRight.pollLast();
while (!staRight.isEmpty()) {
h1 = h2;
h2 = staRight.pollLast();
for (int i = h1 + 1; i < h2; i++)
ans += height[h2] - height[i];
}
return ans;
}
}
(3)结果
执行用时 :3 ms,在所有 Java 提交中击败了 29.32% 的用户;
内存消耗 :38.1 MB,在所有 Java 提交中击败了 54.77% 的用户。
3. 动态规划
(1)思路
对于位置 i,设其左、右长方形最高值为 left[i], right[i],若该位置高度小于左右长方形高度的较小值,则对应存水量为 Math.min(left[i], right[i]) - height[i]。
可结合动态规划方法,预求出各个位置左、右长方形的最高值。
(2)代码
class Solution {
public int trap(int[] height) {
int n = height.length, ans = 0;
//记录各位置对应的左、右长方形最高值
int[] left = new int[n], right = new int[n];
int h = 0;
for (int i = 0; i < n; i++) {
left[i] = h;
h = Math.max(h, height[i]);
}
h = 0;
for (int i = n - 1; i >= 0; i--) {
right[i] = h;
h = Math.max(h, height[i]);
}
//计算各位置存水量
for (int i = 0; i < n; i++) {
h = Math.min(left[i], right[i]);
if (h > height[i])
ans += h - height[i];
}
return ans;
}
}
(3)结果
执行用时 :1 ms,在所有 Java 提交中击败了 99.90% 的用户;
内存消耗 :37.9 MB,在所有 Java 提交中击败了 90.65% 的用户。
4. 双指针
(1)思路
在动态规划方法的基础上,可结合双指针,进一步降低空间复杂度。
在直方图的两端设计 2 个指针,依次移动对应长方形高度较小的指针,则其对应位置的存水量为 左/右指针对应的较小高度 - 当前位置高度。
(2)代码
class Solution {
public int trap(int[] height) {
int n = height.length, ans = 0;
int l = 0, r = n - 1;//左、右指针
while (l < r) {
int h = Math.min(height[l], height[r]);
if (height[l] <= height[r]) {
while (l < r && height[++l] <= h)
ans += h - height[l];
} else {
while (l < r && height[--r] <= h)
ans += h - height[r];
}
}
return ans;
}
}
(3)结果
执行用时 :1 ms,在所有 Java 提交中击败了 99.90% 的用户;
内存消耗 :38.1 MB,在所有 Java 提交中击败了 58.05% 的用户。
三、其他
暂无。