这道题我第一次做的时候我记得是维护单调队列的解法,现在再来看看吧,好的,维护单调队列,这边记录高度的同时,也要记录这一块高度的宽度是多少了,所以用了一个结构体,然后因为出队列是一个先进后出的形式,但是又要访问队首元素,综合考量用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;这样处理可以解决当前块的宽度信息独自记录的问题,还是通过凹槽两边大的减去中间小的这样计算比较直观)。
从接雨水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;
}
};
题解中这个思路感觉说的比较清楚,可以参考: