LeetCode - 42、407 - Trapping Rain Water I - II

本文总结以下两道题目:

42. Trapping Rain Water - 直方图凹陷接雨水问题 (Hard)

407. Trapping Rain Water II - 上题二维变三维 (Hard)

42. Trapping Rain Water

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.


The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1].

In this case, 6 units of rain water (blue section) are being trapped. 

Input: [0,1,0,2,1,0,1,3,2,1,2,1]        Output: 6

解:

    第一反应就是这题不难啊,就用类似扫描线算法,每一个y值计算下这一行能接多少水,每一行只要找到对应 >= y值的最左最右的两个位置,然后中间所有位置,只要比当前 y 值小,盛水数就加一,否则就不加,这样很容易得到结果,不过就是很慢。在下面我的AC代码中,稍微加了一点速,就是最左最右的两个位置的计算不用每次从 0 和 n-1 向里找,而是从上一次的位置继续向里,因为每次找的是第一个 >= y 的最左和最右,所以不会出现找错位置的情况。

int trap(vector<int>& height)
{
    if(height.size() <= 1)   return 0;
    int res = 0;
    int maxh = *max_element(height.begin(), height.end());
    int l = 0, r = height.size() - 1;
    for(int i = 1; i <= maxh; i++)
    {
        while(height[l] < i) ++l;
        while(height[r] < i) --r;
        int tmpl = l + 1;   // l 和 r 要为下一次继续使用,所以需要tmpl用于遍历
        while(tmpl < r)
            if(height[tmpl++] < i)
                ++res;
    }
    return res;
}

    从下边这张图就能看出上边的代码有多慢了(其实是可以再加速的,因为y值不一定是连续的,可能 119911 这种情况123456789的y都需要计算,其实不需要,记录一下哪个y没算然后跳过,可以加速)。 

    上面的代码是一行一行记算能放多少水,但其实观察结果可以看到,结果一定是先高后低这样的趋势,在height最大值的地方最高,如果只有一个最大值,那就是这一个点最高,如果有多个相同最大值,就是一个平行线最高。水的作用就是将凹陷的地方填平,这就想到了一种方法,就是计算所有方块的个数,也就是总体面积,用这个总体的面积减去黑色的方块,也就是height数组占用的方块,就是水的多少。计算黑色方块个数很简单,将凹陷填平也很简单,AC代码如下:

int trap(vector<int>& height)
{
    if(height.size() <= 1)   return 0;
    int res = 0;
    int black_area = accumulate(height.begin(), height.end(), 0);
    int maxh = *max_element(height.begin(), height.end());
    int l = find(height.begin(), height.end(), maxh) - height.begin();      // 最左边maxh的位置
    int r = height.rend() - find(height.rbegin(), height.rend(), maxh) - 1; // 最右边maxh的位置
    // 填平凹陷
    for(int i = 1; i < l; i++)
        if(height[i] < height[i - 1])
            height[i] = height[i - 1];
    for(int i = height.size() - 2; i > r; i--)
        if(height[i] < height[i + 1])
            height[i] = height[i + 1];
    while(l < r)
        height[l++] = maxh;
    // 总体减黑色就是结果  
    return accumulate(height.begin(), height.end(), 0) - black_area;    // 总面积减bar自身占的面积
}

    还有更简单的方式就是,不需要先知道最高的有多高,用两个哨兵分别从最左最右向内收,不断更新最大高度,重点是,要低的位置先向内收(短板效应)。

int trap(vector<int>& height)
{
    int res = 0, l = 0, r = height.size() - 1, maxhl = INT_MIN, maxhr = INT_MIN;
    while(l < r) {
        if(height[l] <= height[r]) {
            if(height[l] > maxhl)   maxhl = height[l];
            else                    res += maxhl - height[l];
            ++l;
        }
        else {
            if(height[r] > maxhr)   maxhr = height[r];
            else                    res += maxhr - height[r];
            --r;
        }
    }
    return res;
}

// 简化版如下
int trap(vector<int>& height)
{
    int l = 0, r = height.size() - 1, tmpmax = 0, res = 0;
    while (l < r) {
        int lower = height[height[l] <= height[r] ? l++ : r--]; // 左右两个哨兵位置上,低的那个位置
        if(lower > tmpmax)  tmpmax = lower;
        else                res += tmpmax - lower;
    }
    return res;
}

    其实我自己觉得上边这个我自己的算法比LeetCode Solution 以及网上的解法都简单,但是还是要记录下边这种用队列来存高度,然后通过高度计算每个位置能存多少水的方法,因为不看懂这个方法,下边那道三维上的题的解法很难理解。。

    这种用队列的思想其实和上边的代码差不多,也是记录目前最高的板子高度,这样能知道后边的每个位置最高能装多高的水。

int trap(vector<int>& height)
{
    int ans = 0, current = 0;
    stack<int> st;
    while (current < height.size()) {
        while (!st.empty() && height[current] > height[st.top()]) {
            int top = st.top();
            st.pop();
            if (st.empty())
                break;
            //printf("current = %d, st.top() = %d\n", current, st.top());
            int distance = current - st.top() - 1;
            int bounded_height = min(height[current], height[st.top()]) - height[top];       
            ans += distance * bounded_height;
            //printf("ans增加 %d\n", distance * bounded_height);
        }
        st.push(current++);
    }
    return ans;
}

    可以通过下图更加直观地理解利用堆栈的算法,根据输出,能够理解为什么第三次增加0,,第四次增加3,就很好理解这个算法了。 

407. Trapping Rain Water II

Given an m x n matrix of positive integers representing the height of each unit cell in a 2D elevation map, compute the volume of water it is able to trap after raining.

Note:
Both m and n are less than 110. The height of each unit cell is greater than 0 and is less than 20,000.

Given the following 3x6 height map:
[
  [1,4,3,1,3,2],
  [3,2,1,3,2,4],
  [2,3,3,2,3,1]
]
   Return 4.

The above image represents the elevation map [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] before the rain.

After the rain, water is trapped between the blocks. The total volume of water trapped is 4.

解:

    这道题和前边的题看山去这不过是二维变三维,但是上题的 “填平后总数减去原有方块数的” 以及 “双指针夹逼” 两种方式就不好实现了。在第一题中基本思路是每次从两端的高度较小的一端移动,这样做的意义在于我们每次都是遍历最短的一个位置,也就是通过短板效应计算能盛水的多少(因为长的板子不影响结果),同时还需要维护一个当前边界的最大值,这样才知道哪个是短的。

    这道题目中,边界从两个点,变成了一圈点,也就是对这一圈点,找一个最矮的,然后遍历,如果有比它矮的,且没有遍历过的,那就说明,这里可以盛水!根据这个思想,能够想到用一个容器记录边界,但是用stack肯定是不行的了,要用到priority_queue,这样能够很方便地找到最矮的板子的位置,因为我们记录方块高度的同时还需要记录位置,所以数据结构就比较复杂了。而且由于STL中,优先队列默认是大根堆,而我们需要小根堆,所以需要下边的第三种写法。

priority_queue<int> q;    // 大根堆
priority_queue<int, vector<int>, less<int> >; // 等价于上边
priority_queue<int, vector<int>, greater<int> >; // 小根堆

    代码如下:

int trapRainWater(vector<vector<int>>& heightMap)
{
    if(heightMap.size()==0) return 0;
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;    // 小根堆
    int rows = heightMap.size(), cols = heightMap[0].size();
    vector<vector<int> > visited(rows, vector<int>(cols, 0));
    int res = 0, tMax = INT_MIN;
    for(int i = 0; i < rows; i++)
        for(int j = 0; j < cols; j++)
            if(i == 0 || i == rows - 1 || j == 0 || j == cols - 1)    // 只遍历边界一圈
            {
                q.push(make_pair(heightMap[i][j], i * cols + j));
                visited[i][j] = 1;                   
            }
    int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上下左右
    while(!q.empty())
    {
        auto t = q.top();
        q.pop();
        int h = t.first, x = t.second / cols, y = t.second % cols;
        tMax = max(tMax, h);
        for(auto d: dir)
        {
            int x2 = x + d[0], y2 = y + d[1];
            if(x2 >= rows || x2 < 0 || y2 < 0 || y2 >= cols || visited[x2][y2]) continue; // 超出边界或已经访问过
            visited[x2][y2] = 1;
            if(heightMap[x2][y2] < tMax)
                res += tMax - heightMap[x2][y2];
            q.push(make_pair(heightMap[x2][y2], x2 * cols + y2));
        }
    }
    return res;
}

猜你喜欢

转载自blog.csdn.net/Bob__yuan/article/details/82698672