42. 接雨水 时间空间优化,双指针,用栈等五种方法

42. 接雨水[hard]

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

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6

思路

       如果走到每一格,知道该格能达到最高的水位就好了。这确实是个好想法,所以该想想怎么去求每格最高水位。

       一种暴力的想法是走到每一格,向左向右遍历找到该格左右两边最高的柱子,就可以了。复杂度达到O(n2),可以考虑空间换时间。

       优化一下第一种方法,得到第二种,用两个数组,一个从左边到右边走,记录小池子最左边最高的柱子,一个从右边到左边走,记录小池子最右边最高的柱子,借助力扣官方题解的图来说明一下,即如下:

        第二种方法借助空间把时间复杂度降到O(n),但是空间开销稍微有点多,可以再优化一下。

        在第二种方法的基础上,优化空间,得到第三种方法。可以用一个数组记录小池子一边最高的柱子,最后一次计算从反方向走,一边走一边得到小池子反方向最高的柱子,即将一个数组降到一个记录某方向最大值的变量。有机整合遍历,减少开销。

       可不可以将另一个数组也优化掉呢?这就需要第四种方法,即双指针。用两个变量记录两个方向路上见到的该侧最高柱子高度,移动较矮的变量,边移动边累加。

       第五种方法和前四种思路不一样,需要用栈,试图将小池子的最侧先压入栈,遇到小池子右侧高柱时,横向一层层累计水量。具体效果,借用力扣windliang的题解图说明一下。

 用栈实现时候,代码不是好理解,可以参考下图。

      综上,前四种的思路基本一致,是立体图形左右视图的思路,需要得到或记录小池子左右两边最高柱,一列列纵向累计水量。第五种方法是横向思路,遇到小池子,从底层到高层一层层累计水量。

     这里给出第三种,第四种和第五种解法的实现。

解法3:数组记从右到左看的右侧最大值,一列列计算

class Solution {
public:
    int trap(vector<int>& height) {
        int len = height.size();
        if (len < 3)
            return 0;
        
        int sum = 0;
        vector<int> rightMax(len);                            //记录从右到左,每格的最大值
        rightMax[len-1] = height[len-1];
        for (int i = len-2; i >= 0; --i)
            rightMax[i] = max(height[i], rightMax[i+1]);
        int leftMax = height[0];                              //记录从左到右,每格的最大值
        for (int i = 1; i < len; ++i) {
            if (height[i] > leftMax)
                leftMax = height[i];
            else
                sum += min(leftMax, rightMax[i]) - height[i];
        }

        return sum;
    }
};

解法4:双指针,左右出发,对称处理

class Solution {
public:
    int trap(vector<int>& height) {
        int len = height.size();
        if (len < 3)
            return 0;
        
        int sum = 0;
        int left = 0, right = len-1;
        int leftMax = 0, rightMax = 0;                //记录左右方向看到的最大值
        while (left < right) {
            if (height[left] <= height[right]) {      //左边较短,先处理左边
                if (height[left] >= leftMax)          //跟新最大左柱高
                    leftMax = height[left];
                else                                  //累计水量
                    sum += leftMax-height[left];
                ++left;
            } else {
                if (height[right] >= rightMax)
                    rightMax = height[right];
                else
                    sum += rightMax-height[right];
                --right;
            }
        }

        return sum;
    }
};

解法5:用栈,对每个小池子一行行累计,先累加底层的水,再一层层往上累加

class Solution {
public:
    int trap(vector<int>& height) {
        int len = height.size();
        if (len < 3)
            return 0;
        
        int sum = 0;
        stack<int> sta;                                                   //没遇见小池子右墙时,先把左边坑的位置压入
        for (int i = 0; i < len; ++i) {
            while (!sta.empty() && height[i] > height[sta.top()]) {       //遇见小池子的右墙
                int top = sta.top();                                      //池底index
                sta.pop();
                if (sta.empty())
                    break;
                
                int high = min(height[sta.top()], height[i]) - height[top];//小池子左右墙能装的相对高度
                int wide = i-sta.top()-1;                                  //小池子当前高度跨越的格子数
                sum += high*wide;
            }
            sta.push(i);
        }

        return sum;
    }
};

猜你喜欢

转载自blog.csdn.net/sy_123a/article/details/109104280