42.接雨水

题目

原题链接:https://leetcode-cn.com/problems/trapping-rain-water/

给定\(n\)个非负整数表示每个宽度为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个单位的雨水(蓝色部分表示雨水)。 

解题思路

维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组\(\textit{height}\)中的元素递减

从左到右遍历数组,遍历到下标\(i\)时,如果栈内至少有两个元素,记栈顶元素为\(\textit{top}\)\(\textit{top}\)的下面一个元素是\(\textit{left}\),则一定有\(\textit{height}[\textit{left}] \ge \textit{height}[\textit{top}]\)

如果\(\textit{height}[i]>\textit{height}[\textit{top}]\)则得到一个可以接雨水的区域,该区域的宽度是\(i-\textit{left}-1\),高度是 \(\min(\textit{height}[\textit{left}],\textit{height}[i])-\textit{height}[\textit{top}]\),根据宽度和高度即可计算得到该区域能接的雨水量。为了得到\(\textit{left}\),需要将\(\textit{top}\)出栈(此时\(\textit{height}[i]>\textit{height}[\textit{top}]\))。在对\(\textit{top}\)计算能接的雨水量之后,\(\textit{left}\)变成新的\(\textit{top}\),重复上述操作,直到栈变为空,或者栈顶下标对应的\(\textit{height}\)中的元素大于或等于\(\textit{height}[i]\)

在对下标\(i\)处计算能接的雨水量之后,或者\(\textit{height}[i]<\textit{height}[\textit{top}]\),则将\(i\)入栈,继续遍历后面的下标,计算能接的雨水量。遍历结束之后即可得到能接的雨水总量。

注意:

\(3,4,5\)依次入栈后,由于\(6\)的高度比\(5\)高,所以\(5\)要出栈,此时计算得到雨量为1:

由于\(6\)的高度和\(4\)的高度一致,所以\(6\)入栈。接着,由于\(7\)的高度比\(6\)的高度高,所以\(6\)出栈,然后计算水量为\(0\)

接着\(4\)出栈:

代码实现

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Deque;
import java.util.LinkedList;

/**
 * 42.接雨水
 * @date 2021/5/17
 * @author chenzufeng
 */

public class No42_TrappingRainWater {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] strings = reader.readLine().split(",");
        int[] heights = new int[strings.length];
        for (int i = 0; i < strings.length; i++) {
            heights[i] = Integer.parseInt(strings[i]);
        }

        System.out.println(trap(heights));
    }

    public static int trap(int[] heights) {
        int ans = 0;
        int length = heights.length;
        Deque<Integer> stack = new LinkedList<>();

        for (int i = 0; i < length; i++) {
            // 当栈不为空,且新d高度大于栈顶所对应的墙高度
            while (! stack.isEmpty() && heights[i] > heights[stack.peek()]) {
                int top = stack.pop();
                // 栈中仅有一个元素,抛出后为空栈
                if (stack.isEmpty()) {
                    break;
                }

                // 栈中至少有两个元素:抛出一个后还有一个
                int left = stack.peek();

                // 计算雨水量
                int currentWidth = i - left - 1;
                int currentHeight = Math.min(heights[left], heights[i]) - heights[top];
                ans += currentHeight * currentWidth;
            }

            // 新高度小于或等于栈顶所对应的高度直接入栈,或者计算完雨量后,新高度入栈
            stack.push(i);
        }

        return ans;
    }
}

复杂度分析

时间复杂度:\(O(n)\),其中\(n\)是数组\(\textit{height}\)的长度。从\(0\)\(n-1\)的每个下标最多只会入栈和出栈各一次。

空间复杂度:\(O(n)\),其中\(n\)是数组\(\textit{height}\)的长度。空间复杂度主要取决于栈空间,栈的大小不会超过\(n\)

posted @ 2021-05-18 23:27  chenzufeng  阅读(78)  评论(0编辑  收藏  举报