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% 的用户。

三、其他

暂无。

猜你喜欢

转载自blog.csdn.net/zml66666/article/details/115406133