leetcode 42. 接雨水(双指针、动态规划、单调栈)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:leetcode 42. 接雨水(双指针、动态规划、单调栈)_第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 个单位的雨水(蓝色部分表示雨水)。

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

思路一:双指针法

  • 首先明确按照行还是按照列计算雨水面积
    leetcode 42. 接雨水(双指针、动态规划、单调栈)_第2张图片
  • 按照列计算,则宽度一定是1,再求每一列雨水的高度即可
  • 每一列雨水的高度取决于该列左侧最高的柱子和右侧最高的柱子之间的最矮柱子的高度,最终每一列雨水的高度=min(lHeight,rHeight)-height
  • 同样的方式,只要从头遍历所有的列,然后求出每一列雨水的面积,相加之后就是总雨水的体积

注:第一个柱子和最后一个柱子不接雨水

代码如下:

class Solution {
public:
    int trap(vector<int>& height) 
    {
        int sum=0;
        for(int i=0;i<height.size();i++)
        {
            //第一个柱子和最后一个柱子不接雨水
            if(i==0||i==height.size()-1)
            {
                continue;
            }
            int lheight=height[i]; //记录左边柱子的最高高度
            int rheight=height[i]; //记录右边柱子的最高高度
            for(int j=i-1;j>=0;j--)
            {
                if(lheight<height[j])
                {
                    lheight=height[j];
                }
            }
            for(int j=i+1;j<height.size();j++)
            {
                if(rheight<height[j])
                {
                    rheight=height[j];
                }
            }
            
            int h=min(lheight,rheight)-height[i];
            if(h>0)
            {
                sum+=h;
            }
        }
        return sum;
    }
};

注:这样写适用于数据量较小情景,但数据量过大时,会出现超出时间限制的情况
leetcode 42. 接雨水(双指针、动态规划、单调栈)_第3张图片

  • 时间复杂度O(N^2)
  • 空间复杂度O(1)

思路二:动态规划解法

为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。

  • 将每个位置的左边最高高度记录在一个数组中,将右边最高高度记录在另一个数组中,这样可以避免重复计算
  • 当前位置的左边最高高度是前一个位置的左边最高高度和本高度比较后的最大值

代码如下:

class Solution {
public:
    int trap(vector<int>& height) 
    {
        // if(height.size()<=2)
        // {
        //     return 0;
        // }
        vector<int>Maxlh(height.size(),0);
        vector<int>Maxrh(height.size(),0);
        int sum=0;
        //记录每个柱子左边柱子的最大高度
        Maxlh[0]=height[0];
        for(int i=1;i<height.size();i++)
        {
            Maxlh[i]=max(Maxlh[i-1],height[i]);
        }
        //记录每个柱子右边柱子的最大高度
        Maxrh[height.size()-1]=height[height.size()-1];
        for(int i=height.size()-2;i>=0;i--)
        {
            Maxrh[i]=max(Maxrh[i+1],height[i]);
        }
        //求和
        for(int i=0;i<height.size();i++)
        {
            int h=min(Maxlh[i],Maxrh[i])-height[i];
            sum+=h;
        }
        return sum;
    }
};

  • 时间复杂度O(N)
  • 空间复杂度O(N)

注:思路二显然是以空间换时间

(推荐)思路三:单调栈解法

单调栈就是保持栈内元素有序,需要自己实现

  • 单调栈按照行方向计算雨水面积

  • 从栈头到栈尾的元素应该是从小到大的顺序

  • 因为一旦发现添加的柱子高度大于栈头元素,就说明此时出现了凹槽,栈头元素就是凹槽底部的柱子,栈头的第二个元素就是凹槽底部左边的柱子,而添加的元素就是凹槽底部右边的柱子

  • 遇到相同的元素就更新栈内元素,即将栈内元素弹出,将新元素加入栈(因为求宽度的时候如果遇到相同高度的柱子,则需要使用最右边的柱子计算宽度)

  • 如果当前柱子的高度小于栈顶元素的高度,就把这个元素入栈(因为栈中的元素本来就要保持从小到大的顺序)

  • 雨水的高度=min(凹槽左边高度,凹槽右边高度)-凹槽底部高度

  • 雨水宽度就是凹槽右边的下标-凹槽左边的下标-1

  • 当前凹槽雨水的面积=雨水的高度*雨水的宽度

代码如下:

class Solution 
{
public:
    int trap(vector<int>& height) 
    {
        if(height.size()==0)
        {
            return 0;
        }
        int sum=0;
        stack<int> st;
        st.push(0);
        for(int i=1;i<height.size();i++)
        {
            while(!st.empty()&&height[i]>height[st.top()])
            {
                int mid=st.top();
                st.pop();
                if(!st.empty())
                {
                    int h=min(height[i],height[st.top()])-height[mid];
                    int w=i-st.top()-1;
                    sum+=(h*w);
                }
            }
            st.push(i);
        }
        return sum;
    }
};

注:上面的代码看上去好像只处理的是情况三,其实是把情况一和情况二融合了

  • 时间复杂度O(N)
  • 空间复杂度O(N)

你可能感兴趣的:(leetcode,深度优先,算法,牛客网,leetcode,c++)