42.接雨水(单调栈/双向记录前后缀/双指针) 407.接雨水2(优先队列+bfs)

42.接雨水

这道题我第一次做的时候我记得是维护单调队列的解法,现在再来看看吧,好的,维护单调队列,这边记录高度的同时,也要记录这一块高度的宽度是多少了,所以用了一个结构体,然后因为出队列是一个先进后出的形式,但是又要访问队首元素,综合考量用deque比较合适。

具体queue和deque的区别见 https://zhuanlan.zhihu.com/p/77981148

class Solution {
public:
    struct node
    {
        int h,x;
        node(int h,int x):h(h),x(x){}
    };
    int trap(vector<int>& height) {
        int n=height.size();
        if (n==0) return 0;
        deque<node> dq;
        node w(height[0],1);
        dq.push_back(w);
        int ans=0;

        for (int i=1;i<n;i++)
        {
            if (height[i]>=dq.front().h)
            {
                int hnow=dq.front().h;
            
                while (!dq.empty())
                {
                    ans+=(hnow-dq.back().h)*dq.back().x;
                    dq.pop_back();
                }

                dq.push_back(node(height[i],1));
            }
            else
            {
                int hnow=height[i];
                int xnow=1;
            
                while (height[i]>=dq.back().h)
                {
                    ans+=(hnow-dq.back().h)*dq.back().x;
                    xnow+=dq.back().x;
                    dq.pop_back();
                }

                dq.push_back(node(height[i],xnow));
            }
        }
        return ans;
    }
};

另外貌似还有双向记录的方法,我们再来康康,即记录前缀最大和后缀最大的方式,跟有一道记录前缀和还有记录后缀和的思路一样。即题目原意是对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值,那么

算法

找到数组中从下标 i 到最左端最高的条形块高度 left_max。
找到数组中从下标 i 到最右端最高的条形块高度 right_max。
扫描数组 height 并更新答案:
累加 min(max_left[i],max_right[i])−height[i] 到 ans 上

class Solution {
public:
    int trap(vector<int>& height)
    {
        if(height.size() == 0)
            return 0;
        int ans = 0;
        int size = height.size();
        vector<int> left_max(size), right_max(size);
        left_max[0] = height[0];
        for (int i = 1; i < size; i++) {
            left_max[i] = max(height[i], left_max[i - 1]);
        }
        right_max[size - 1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            right_max[i] = max(height[i], right_max[i + 1]);
        }
        for (int i = 1; i < size - 1; i++) {
            ans += min(left_max[i], right_max[i]) - height[i];
        }
        return ans;
    }

};

但是这要开销O(2n)的空间复杂度,用deque只要O(n),但是如果面试的时候遇到这道题,建议采用这种双向记录的方法,比较好理解并且编码比较简单。

好的,我找到那道记录前缀乘积和后缀乘积的题了   238.除自身以外数组的乘积   这边稍微对比记录一下。

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n=nums.size();
        vector<int> ans(n);
        vector<int> left_mul(n),right_mul(n);
        left_mul[0]=1;
        for (int i=1;i<n;++i)
        left_mul[i]=nums[i-1]*left_mul[i-1];
        right_mul[n-1]=1;
        for (int i=n-2;i>=0;--i)
        right_mul[i]=nums[i+1]*right_mul[i+1];

        for (int i=0;i<n;i++)
        ans[i]=left_mul[i]*right_mul[i];

        return ans;

    }
};

另外还有一种双指针的算法,非常巧妙。

定理一:在某个位置i处,它能存的水,取决于它左右两边的最大值中较小的一个。

定理二:当我们从左往右处理到left下标时,左边的最大值left_max对它而言是可信的,但right_max对它而言是不可信的。

定理三:当我们从右往左处理到right下标时,右边的最大值right_max对它而言是可信的,但left_max对它而言是不可信的。

对于位置left而言,它左边最大值一定是left_max,右边最大值“大于等于”right_max,这时候,如果left_max<right_max成立,那么它就知道自己能存多少水了。无论右边将来会不会出现更大的right_max,都不影响这个结果。 所以当left_max<right_max时,我们就希望去处理left下标,反之,我们希望去处理right下标。

class Solution {
public:
    int trap(vector<int>& height)
    {
        int left = 0, right = height.size() - 1;
        int ans = 0;
        int left_max = 0, right_max = 0;
        while (left <= right) {
            if (left_max < right_max) {
                height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]);
                ++left;
            }
            else {
                height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]);
                --right;
            }
        }
        return ans;
    }

};

总结:其实我最开始的写法有点长,其实只要单调stack就能解决,不需要判断栈底的元素,只需要跟栈顶的元素一次比较,然后存下标就行了,因为存高度的话没办法记录下标宽度信息,但如果记录下标i,那么通过height[i]就可以得到高度信息,通过下标相减得到宽度信息。(注意这里相减的是top()-1的相减,即 栈中为ab当前为c,并且height[b]<heigth[c]那么dis=c-a+1;这样处理可以解决当前块的宽度信息独自记录的问题,还是通过凹槽两边大的减去中间小的这样计算比较直观)。

407.接雨水2

从接雨水1可以看到必须是一个凹坑才能积水,维护的是一维上的,而这边则是要维护二维上的,原来维护两边,现在要维护一个圈,这是第一难点,第二个难点是很难想到怎么维护,这边是用优先队列找到这个圈的最短的那块板,逐步向内灌水,神奇的是原来一个圈,如果逐步向内,是不会破坏圈的性质的,相当于向四个方向灌水,会重新建立起木板,这道题用到的模板还是走迷宫的bfs的模板,而且从queue变成priority_queue而已,这样看来就是普通的优先队列+bfs了。

class Solution {
private:
    int foot[4][2]={-1,0,1,0,0,-1,0,1};

    struct node
    {
        int x,y,h;
        node(int x,int y,int h):x(x),y(y),h(h) {}
        friend bool operator < (node a,node b)
        {
            return a.h>b.h;
        }
    };
public:
    int trapRainWater(vector<vector<int>>& heightMap) {
        if (heightMap.size()==0 || heightMap[0].size()==0) return 0;
        int n=heightMap.size(),m=heightMap[0].size();
        priority_queue<node> q;
        bool vis[n+10][m+10];
        memset(vis,0,sizeof(vis));
        vector<vector<int>> hm(n+10,vector<int>(m+10,0));
        for (int i=0;i<=n+1;i++)
        for (int j=0;j<=m+1;j++)
        {
            if (i==0 || i==n+1 || j==0 || j==m+1)
                vis[i][j]=true;
            else
            {
                hm[i][j]=heightMap[i-1][j-1];
                if (i==1 || i==n || j==1 || j==m)
                {
                    q.push({i,j,hm[i][j]});
                    vis[i][j]=true;
                }
            }
        }
        int ans=0;
        while (!q.empty())
        {
            node w=q.top();
            q.pop();
            for (int i=0;i<4;i++)
            {
                int x=w.x+foot[i][0];
                int y=w.y+foot[i][1];
                if (!vis[x][y])
                {
                    if (hm[x][y]>=w.h)
                    {
                        q.push({x,y,hm[x][y]});
                        vis[x][y]=true;
                    }
                    else
                    {
                        ans+=w.h-hm[x][y];
                        q.push({x,y,w.h});
                        vis[x][y]=true;
                    }
                }
            }
        }
        return ans;
    }
};

注意:

1.数组vis和hm都记得开大一点

2.q.push({i,j,hm[i][j]});是一种写法,q.push(node(i,j,hm[i][j]));应该也是一种写法。

然后因为我把原矩阵从[0..n-1][0..m-1]移到了[1..n][1..m]所以写得有些麻烦,如果不移可能写得更简单,见下。

class Solution {
private:
    int foot[4][2]={-1,0,1,0,0,-1,0,1};

    struct node
    {
        int x,y,h;
        node(int x,int y,int h):x(x),y(y),h(h) {}
        friend bool operator < (node a,node b)
        {
            return a.h>b.h;
        }
    };
public:
    int trapRainWater(vector<vector<int>>& heightMap) {
        if (heightMap.size()==0 || heightMap[0].size()==0) return 0;
        int n=heightMap.size(),m=heightMap[0].size();
        priority_queue<node> q;
        bool vis[n+10][m+10];
        memset(vis,0,sizeof(vis));

        for (int i=0;i<n;i++)
            for (int j=0;j<m;j++)
            if (i==0 || i==n-1 || j==0 || j==m-1)
            {
                q.push(node(i,j,heightMap[i][j]));
                vis[i][j]=true;
            }
        int ans=0;
        while (!q.empty())
        {
            node w=q.top();
            q.pop();
            for (int i=0;i<4;i++)
            {
                int x=w.x+foot[i][0];
                int y=w.y+foot[i][1];
                if (x>=0 && x<n && y>=0 && y<m && !vis[x][y])
                {
                    if (heightMap[x][y]<w.h)
                        ans+=w.h-heightMap[x][y];
                    q.push(node(x,y,max(w.h,heightMap[x][y])));
                    vis[x][y]=true;
                }
            }
        }
        return ans;
    }
};

题解中这个思路感觉说的比较清楚,可以参考:

https://leetcode-cn.com/problems/trapping-rain-water-ii/solution/407-jie-yu-shui-zui-xiao-dui-cjie-fa-by-he-qian-3/

猜你喜欢

转载自blog.csdn.net/hbhhhxs/article/details/107805446
今日推荐