Leetcode 面试题 直方图的水量

题目传送门

解题思路:

解法一:暴力

  先来看一下如何才能得到我们的答案,我们需要知道对于每个格子来说,其能够容纳的水量是由什么来决定的。通过对图例的观察我们会发现,每个格子容纳的水量是由其左右两边最大高度的较小者减去该格子的高度,即\(min(l_{max},r_{max})-height[i]\)。从而我们就可以得到一个暴力算法,对每个格子都计算一下其左右高度的最大值,最后累加到结果里就可以。
  时间复杂度:该算法需要遍历所有格子,对于每个格子还需要遍历其左右两边格子,因此时间复杂度为\(O(N^2)\)
  空间复杂度:没有使用额外的空间,因此为\(O(1)\)

代码
class Solution {
public:
    int trap(vector<int>& height) {
        if(height.empty()) return 0;

        int res=0;

        for(int i=0;i<height.size();i++)
        {
            int lmax=0;
            for(int j=i-1;j>=0;j--) lmax=max(lmax,height[j]);
            int rmax=0;
            for(int j=i+1;j<height.size();j++) rmax=max(rmax,height[j]);

            int t=min(lmax,rmax)-height[i];
            res+=t>0?t:0;
        }
        return res;
    }
};

解法二:预处理

  虽然上面的算法可以通过本题,但是如果题目中的时空限制较为严格,就无法通过了,因此我们来思考如果优化我们的暴力算法。暴力算法中耗时的地方在于,我们对每个格子都需要计算一遍左右高度的最大值,而我们完全可以开两个数组来分别存储其左边高度的最大值与右边高度的最大值,在循环中查表即可。
  时间复杂度:经过预处理,需要\(O(N)\)的时间,之后在计算的时候只要遍历即可,需要\(O(N)\)的时间,因此整体的时间复杂度为\(O(N)\)
  空间复杂度:由于我们需要开辟新的数组来存储左右最大高度,因此空间复杂度为\(O(N)\)

代码
class Solution {
public:
    int trap(vector<int>& height) {
        if(height.empty()) return 0;

        int n=height.size();
        vector<int> lmax(n),rmax(n);
        //预处理左边高度
        lmax[0]=height[0];
        for(int i=1;i<n;i++)
        {
            lmax[i]=max(lmax[i-1],height[i]);
        }
        //预处理右边高度
        rmax[n-1]=height[n-1];
        for(int i=n-2;i>=0;i--)
        {
            rmax[i]=max(rmax[i+1],height[i]);
        }
        //计算结果
        int res=0;
        for(int i=0;i<n;i++)
        {
            res+=min(lmax[i],rmax[i])-height[i];
        }
        return res;
    }
};

解法三:单调栈

  前面的算法都是直接去计算当前这个格子能够存储的水量。我们来尝试一下如何能够在遍历的时候就开始装水进去。我们在遍历的时候,其实是不知道现在这个格子具体能够装多少水的(没有预处理的情况下),我们可以确定的是,当我们遇到一个高度比之前高的格子时,这个格子左边比它低且有左边界之间的空间时能够被水填满的,比如\([1,0,1]\),当我们遇到后一个\(1\)的时候,我们就能够确定其最近一个比它低的格子到其左边界的位置之间是能够被填上水的。
  对于这种情况我们就可以用一个单调栈来维护,我们维护一个数组的下标,从栈底到栈顶的对应下标的高度是递减的。我们从左向右开始遍历数组,如果当前栈内至少有两个数,我们记栈顶元素为\(top\),其下一个元素为\(left\),根据性质,我们会有\(height[left]\ge height[top]\),如果有\(height[i]>height[top]\),那么我们就知道是可以填水进去的,填水的高度就是\(min(height[left],height[i])-height[top]\),由于单调栈记录的是下标,因此我们就可以得到填充水域的宽度为\(i-left-1\),宽度与高度相乘后添加到结果中即可。
  时间复杂度:由于数组中的每个元素只会进栈一次,因此时间复杂度为\(O(N)\)
  空间复杂度:空间复杂度取决于单调栈的大小,栈的大小不会超过数组长度,因此为\(O(N)\)

代码
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> stk;
        
        int res=0;
        for(int i=0;i<height.size();i++)
        {
            while(!stk.empty()&&height[stk.top()]<height[i])
            {
                int top=stk.top();
                stk.pop();

                if(stk.empty()) break;//如果栈内没有两个元素,说明没有空间填水
                int left=stk.top();
				
                int fillHeight=min(height[left],height[i])-height[top];
                int fillWidth=i-left-1;

                res+=fillHeight*fillWidth;
            }
            stk.push(i);
        }
        return res;
    }
};
posted @ 2021-04-09 16:16  Daneii  阅读(85)  评论(0)    收藏  举报