力扣第42题:接雨水——单调栈

LeetCode第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 个单位的雨水(蓝色部分表示雨水)。

力扣第42题:接雨水——单调栈_第1张图片
思路:首先,我们先明确可以接多少雨水是怎么计算出来的?有横竖两种方向可以看。如下图所示:
横向:
力扣第42题:接雨水——单调栈_第2张图片
纵向:
力扣第42题:接雨水——单调栈_第3张图片
这里以纵向为例:
第0列可以接得的雨水量为:0 (因为左边没有柱子比第0列的柱子高)
第1列可以接得的雨水量为:0 (因为左边没有柱子比第1列的柱子高)
第2列可以接得的雨水量为:1 (左边第1列的柱子比第2列高,右边第3列的柱子比第2列的柱子高,于 是第1,2,3列形成宽为1高也为1的凹槽)

第3列可以接得的雨水量为:0 (因为左边没有柱子比第3列的柱子高)
对于第4、5、6列,很明显能形成凹槽,于是我们可以按列分割这个凹槽如下:
第4列可以接得的雨水量为:1
第5列可以接得的雨水量为:2
第6列可以接得的雨水量为:1

第7列可以接得的雨水量为:0 (因为左边没有柱子比第7列的柱子高, 右边没有柱子比第7列的柱子高)
第8列可以接得的雨水量为:0 (右边没有柱子比第8列的柱子高)
第9列可以接得的雨水量为:1
第10列可以接得的雨水量为:0 (右边没有柱子比第10列的柱子高)
第11列可以接得的雨水量为:0 (因为右边没有柱子比第11列的柱子高)
从上面我们可以找到一些规律:

  • 我们以列为单位去分割总任务,最后要求总的接水量,其实就是把每一列的接水量加起来的总和。
  • 要想要能接水,那么左边的柱子高度必须严格大于该列的高度,右边的柱子也必须严格大于该列高度(即:左右与该列要能形成凹槽)。

我们可以使用单调栈。

  • 确定栈的顺序。
    我们首先要确定栈内元素是递增还是递减。根据上面两个规律,我们可以得出栈内元素是递减(从栈底到栈顶)。
  • 确定存放内容
    这里存放元素下标比较方便,因为我们需要计算凹槽宽高,而元素我们可以直接使用下标访问得到。
  • 处理逻辑力扣第42题:接雨水——单调栈_第4张图片
    此时栈内是递减的,下一个将要进站的元素是下标为6的元素(高度为1),如果它直接进栈就会破坏单调栈,因此,需要元素出栈,那么下标为5的元素就要出栈,在它出栈的同时,我们可以直接计算出这个出栈的元素可以收集的雨水量,但是有一个注意点是,在这里每个小凹槽按行收集比按列收集要方便。
    首先,我们要记得我们维护的栈是一个非递增的栈。也就是说只有将要进栈的元素严格大于栈顶元素我们不会让其进栈。假设将要进栈的元素比栈顶元素大,那么我们会选择栈顶元素出栈,计算其接水量。换句话说,这个将要进栈的元素就成为了右挡板
    力扣第42题:接雨水——单调栈_第5张图片
    将要进栈的元素为第7号,此时栈顶是第6号元素,将要进栈元素比栈顶元素大,那么第7号元素就成为了第6号元素的右挡板,此时我们要找第6号的左挡板,其左挡板一定是在栈内元素中的。此时,栈内元素一定是大于等于第6号元素的。我们可以假设出栈后的栈顶是其左挡板,那么其接水量就为((将要进栈元素下标 - 出栈后栈顶元素下标) - 1) ×(min(左挡板, 右挡板) - 出栈元素)。即((7-4)-1×( min(1, 3) - 1) = 0。也就是说4号元素作为左挡板不能接水(必定是因为左挡板和中间高度相同,这是由于单调栈的特性决定的),那么我们继续出栈,这过程也就是找寻左挡板的过程。如上面这幅图所示,我们找到第3号元素就是其左挡板,可以计算出其接水量。

总结

  • 我们其实在总体遍历的过程中是按列遍历的,然后不断找到可以接水的凹槽,对于每个凹槽我们其实是按行的方式去计算出其盛水量的,我们在出栈的过程中其实是不断找寻左挡板的过程,这过程中会遇见接不了水的情况(我们也可以认为接水量为0,依旧把他加入最后结果中)。
  • 代码
int trap(vector<int>& height) {
    stack<int> st; //单调栈内存放元素下标
    st.push(0);  //放入第一个元素
    int sum = 0;
    for (int i = 1; i < height.size(); ++i) {
        if (st.empty() || height[i] <= height[st.top()]) {
            st.push(i);
        }
        else {
            while (!st.empty() && height[i] > height[st.top()]) {
                int index = st.top(); //栈顶元素出栈并记录下标
                st.pop();
                if (!st.empty()) {
                    int w = i - st.top() - 1;           //凹槽宽
                    int h = min(height[i], height[st.top()]) - height[index]; //凹槽高
                    sum += w * h;       //累加所有接水量
                }
            }
        }
        st.push(i);
    }
    return sum;
}

你可能感兴趣的:(算法刷题,leetcode,算法)