LeetCode刷题05-42. 接雨水 tag 数组 hard 407. 接雨水 II hard

 

 

四种解法

一、左右指针对撞

思路:左右比较取小值,将lower与记录的水位线进行比较,如果水位线高于lower,则水位线设为level=lower,然后记录该处可存水量water+=level-lower。这里相当于,时刻记录左右两边的最小值作为水位线,然后用水位线减去本处高度作为存水量。

class Solution {
public:
    int trap(vector<int>& height) {
        int n=height.size();
        int l=0,r=n-1,lower=0,level=0;
        int water=0;
        while(l<r)
        {
            lower=min(height[l],height[r]);
            if(height[l]<height[r])
                l++;
            else 
                r--;
            level=max(lower,level);
            water+=level-lower;
        }
        return water;
    }
};

 

二、按照列求
思路
对每一列来说,都判断自己这一列能不能装水,如何判断呢?
看自己左边最高的墙(不包括自己)和右边最高的墙(不包括自己),根据短板效应取这俩中更小的那个值lowWall
lowWall比自己高那才可能出现凹坑才能存水对吧,旁边的比自己矮那不流出去了
然后计算这一列上存了多少水
首先两端的墙不用考虑,存不了,直接从第二个看
比如下面红色这一列能存几个水呢?

1.红色自己高度是1,左边最高是绿色,高度为0,右边最高是紫色,值为3,根据短板效应取lowWall = 0,
2.lowWall没有自己高,装不了


来看个能装的例子,下图我们来看红色框里这一列,它能装多少水呢?
首先,左边最高的墙是绿色,高度为2,右边最高的墙为紫色,高度为3,lowWall取值为2
lowWall大于红色自身高度1所以能装啊

那每列能装多少水呢?根据上图看出来,是(lowWall-自身高度)
最后按照这个方法计算每一列就完事了

逐句注释的代码实现

public int trap(int[] height) {
    int sum = 0;
    //最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2
    for (int i = 1; i < height.length - 1; i++) {
        int max_left = 0;
        //找出左边最高
        for (int j = i - 1; j >= 0; j--) {
            if (height[j] > max_left) {
                max_left = height[j];
            }
        }
        int max_right = 0;
        //找出右边最高
        for (int j = i + 1; j < height.length; j++) {
            if (height[j] > max_right) {
                max_right = height[j];
            }
        }
        //找出两端较小的
        int min = Math.min(max_left, max_right);
        //只有较小的一段大于当前列的高度才会有水,其他情况不会有水
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

时间复杂度:O(n²),遍历每一列需要 nn,找出左边最高和右边最高的墙加起来刚好又是一个 n,所以是 n²。
空间复杂度:O(1)

三、动态规划(优化刚才的按列)
思路
刚才代码中出现了一些重复出现的循环遍历,每次计算新的一列时候,我们都要重新找两边最高的墙,完全没必要啊,利用动态规划的思想,把这些信息放到一个数组里就ok,动态规划的思想很复杂,在这里说不清,以后再详细讲解,反正大概就是把重复的方法归纳起来复用

逐句注释的代码实现

public int trap(int[] height) {
    //sum计算总和
    // 新建两个长度和height一样的数组用来存 每一列的左右最高墙在height中的索引
    int sum = 0;
    int[] max_left = new int[height.length];
    int[] max_right = new int[height.length];
    
    //求所有列的左边最高墙  索引都放在max_left里
    for (int i = 1; i < height.length - 1; i++) {
        max_left[i] = Math.max(max_left[i - 1], height[i - 1]);
    }

    //求所有列的右边最高墙  索引都放在max_right里
    for (int i = height.length - 2; i >= 0; i--) {
        max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
    }

    //计算每一列的能存多少水累加起来   这一步和上面方法没区别
    for (int i = 1; i < height.length - 1; i++) {
        int min = Math.min(max_left[i], max_right[i]);
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

动态规划优化了求一侧最大值的操作,其余代码没变化
时间复杂度:O(n)
空间复杂度:O(n),用来保存每一列左边最高的墙和右边最高的墙。
相比刚才的方法,时间复杂度降了下来,空间复杂度略有上升

四、双指针(把刚才上升的空间复杂度给降下来成为指标最优的解法)
思路
这道题中,可以看到,max_left[ i ] 和 max_right[ i ] 数组中的元素我们其实只用一次,然后就再也不会用到了。所以我们可以不用数组,只用一个元素就行了。

逐句注释的代码实现
我们先改造下 max_left

public int trap(int[] height) {
    int sum = 0;
    int max_left = 0;

    //右侧的没改还用数组存
    int[] max_right = new int[height.length];
    for (int i = height.length - 2; i >= 0; i--) {
        max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
    }

    //左侧的改了
    for (int i = 1; i < height.length - 1; i++) {
        max_left = Math.max(max_left, height[i - 1]);
        int min = Math.min(max_left, max_right[i]);
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

时间复杂度: O(n)
空间复杂度:O(1)

五、栈

思路

这道题好像就是想考栈= =
就是把墙的高度压入栈中,栈顶元素就是上一个墙的高度,如果指针指的当前墙比上一个墙高,那就说明,能存水,没有上一个墙高,那就存不了水,把这个墙放进去
具体思路都写代码注释里面了,注释已经不能再细了

逐句注释的代码实现

public int trap(int[] height) {

        /**
         * 初始栈
         * 初始总和
         * 初始指针指向第一个元素
         */
        Stack<Integer> stack = new Stack<>();
        int sum = 0;
        int currIndex = 0;

        /**
         * 最后用大循环包围
         */
        while (currIndex < height.length) {
            /**
             * 如果下一个比栈顶的大,那就进行一系列操作
             */
            while (!stack.empty() && height[currIndex] > height[stack.peek()]) {
                /**
                 * 1.首先取出栈顶元素 是计算面积的底
                 * 2.然后刚才的底没用了,出栈,我们要他之前的那个墙(新的栈顶)的高度,和currIndex指针指向的墙比,哪个矮,哪个-底就等于计算面积的高
                 *                  (矮墙-底) X 底边宽 = 面积
                 * 3.底边宽怎么求?  底边宽 = 后面的墙-前面的墙-1
                 * 4.所以最终 sum = 面积 = (矮墙-底) X 底边宽
                 */

                //1.
                int bottomHeight = height[stack.peek()];

                //2.
                stack.pop();
                //这里注意 要是出栈以后 栈空了那就跳出循环进行下一个height里的元素操作
                if (stack.empty()) break;
                //go on
                int lowerWall = Math.min(height[currIndex], height[stack.peek()]);

                //3.
                int bottomWide = currIndex - stack.peek() - 1;

                //4.
                sum = sum + (lowerWall - bottomHeight) * bottomWide;


            }
            /**
             * 如果下一个比栈顶的小,那就直接入栈
             */
            stack.push(currIndex);


            /**
             * 遍历记得指针往后走
             */
            currIndex++;


        }


        return sum;
    }

}

 单调栈,c++代码:

栈内从栈底到栈顶单调递减。所以当height[i]<=height[s.top()]时,直接压栈。当height[i]>height[s.top()]时,可以蓄水。这时,先出栈k=s.top(),s.pop(),然后计算以bottom=height[k]为底的凹槽蓄水量

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

 


————————————————
版权声明:本文为CSDN博主「RookieZc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41132565/article/details/109160614

 

407. 接雨水 II

题目:

给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。

 

示例 1:

 

 

 

输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。

 

思路:

这里不能单纯按照上下左右的最大值来求解。

这是因为储水区域是联通的,例如

12 13 1 12

13 4 13 12

13 8 10 12

12 13 12 12

针对该矩阵,结果为14而不是15.这是因为对于4来说,因为12-10-8-4的联通水域,使得4处的储水高度为12-4.

所以求解该题,应该使用联通区域深度遍历的方式。

用小根堆记录四边,然后每次取出最小的点求联通的区域,并记录访问过的联通区域。

class Solution {
public:
    struct State{
        State(int h,int x,int y){
            this->h=h;
            this->x=x;
            this->y=y;
        }
        int h;
        int x;
        int y;
        // 小根堆
        bool operator<(const State& s) const{
            return h>s.h;
        }
    };
    int trapRainWater(vector<vector<int>>& heightMap) {
        priority_queue<State> pq;
        int m=heightMap.size();
        int n=heightMap[0].size();
        // 记录访问过的点
        vector<vector<bool>> visited(m,vector<bool>(n,false));
        // 将左右两边放入堆
        for(int i=0;i<m;++i){
            pq.push(State(heightMap[i][0],i,0));
            pq.push(State(heightMap[i][n-1],i,n-1));
            visited[i][0]=true;
            visited[i][n-1]=true;
        }
        // 将上下两边放入堆
        for(int j=0;j<n;++j){
            pq.push(State(heightMap[0][j],0,j));
            pq.push(State(heightMap[m-1][j],m-1,j));
            visited[0][j]=true;
            visited[m-1][j]=true;
        }
        int ret=0;
        int dist[][2]={{0,1},{0,-1},{1,0},{-1,0}};
        while(!pq.empty()){
            State s=pq.top();
            pq.pop();
            int h=s.h;
            int nx=s.x;
            int ny=s.y;
            for(int i=0;i<4;++i){
                // 求四周的联通点
                int x=nx+dist[i][0];
                int y=ny+dist[i][1];
                // 如果合法且未被访问过
                if(x>=0&&x<m&&y>=0&&y<n&&!visited[x][y]){
                    // 如果可以储水,记录
                    if(h>heightMap[x][y])
                        ret+=h-heightMap[x][y];
                    visited[x][y]=true;
                    // 这里放入时记录最大高度
                    pq.push(State(max(h,heightMap[x][y]),x,y));
                }
            }
        }
        return ret;
    }
};

 

posted @ 2021-03-09 20:21  鸭子船长  阅读(78)  评论(0编辑  收藏  举报