LeetCode/接雨水
给定n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
一. 接雨水I
1. 暴力求解
根据每一个柱子左右两端最高的柱子,计算其蓄水量,然后把总的加起来,时间复杂度为O(n2)
暴力双重循环
class Solution {
public:
int trap(vector<int>& height){
int n = height.size();
int ans = 0;
for(int i = 1;i<n-1;i++){
int l_max = 0,r_max = 0;
//找右边最高的柱子
for(int j=i;j<n;j++)
r_max = max(r_max,height[j]);
//找左边最高的柱子
for(int j=i;j>=0;j--)
l_max = max(l_max,height[j]);
ans += min(l_max,r_max) - height[i];
}
return ans;
}
};
2.暴力求解优化
先用备忘录计算每个位置左右两端最高柱子高度,避免重复计算,时间复杂度为O(n),空间复杂度为O(n)
备忘录预处理
class Solution {
public:
int trap(vector<int>& height){
int n = height.size();
int ans = 0;
vector<int> rmax(n+1);
vector<int> lmax(n+1);
for(int i=0;i<n;i++){
lmax[i+1] = max(lmax[i],height[i]);//计算左侧最大值
rmax[n-i-1] = max(rmax[n-i],height[n-i-1]);//计算右侧最大值
}
for(int i = 1;i<n-1;i++)
ans += min(lmax[i+1],rmax[i]) - height[i];
return ans;
}
};
3.双指针交替计算每个柱子
相当于算法2的进一步优化,舍去了储存两端最高高度的数组,只记录当下遍历过的左右端最高高度,之所以能这么计算,是当我们判断左边最高高度高于右边时,这时我们就能算出右边当下端点的储水量,值为右边最大高度-右边当下端点高度,然后继续移动右边端点,交替计算另一方的储水量,这样就不用把全部端点左右最大高度存储起来,以节省存储空间。
class Solution {
public:
int trap(vector<int>& height) {
int n =height.size();
int left = 0,right = n-1;
int ans = 0;
int l_max = height[0];
int r_max = height[n-1];
while(left<=right){
l_max = max(l_max,height[left]);
r_max = max(r_max,height[right]);
if(l_max<r_max){
ans += l_max - height[left];
left++;
}
else{
ans += r_max - height[right];
right--;
}
}
return ans;
}
};
双指针堆积木计算总体积(高度上升,把上升的那层体积加上)
class Solution {
public:
int trap(vector<int>& height) {
int n =height.size();
int high = 0; int buff = 0;int volume = 0;int solid = 0;
for(int i=0,j=n-1;i<=j;){
if(min(height[i],height[j])>high){
buff = min(height[i],height[j]);//暂存当前蓄水高度,因为后面还要用到之前高度做差
volume = (buff-high)*(j-i+1)+volume;//计算总体积
high = buff;
}
if(height[i]<=height[j]) i++;
else j--;
}
for(int i =0;i<n;i++)
solid = solid +height[i];//计算柱子体积
return volume-solid;
}
};
二. 接雨水II(小根堆+贪心)
从外圈开始,贪心选择短板,计算内部相邻柱子,计算储水量,并进行短板替换,使圈子内缩
typedef pair<int,int> pii;
class Solution {
public:
int trapRainWater(vector<vector<int>>& heightMap) {
int m = heightMap.size();
int n = heightMap[0].size();
if(m<=2||n<=2) return 0;
priority_queue<pii, vector<pii>, greater<pii>> pq;//小根堆(按高度排)存储高度和坐标
vector<vector<bool>> visit(m, vector<bool>(n, false));
for (int i = 0; i < m; i++) //最外圈边界入堆
for (int j = 0; j < n; j++)
if (i == 0 || i == m - 1 || j == 0 || j == n - 1){
pq.push({heightMap[i][j], i * n + j});//坐标一维化
visit[i][j] = true;//标记访问过的
}
int res = 0;
int dirs[] = {-1, 0, 1, 0, -1};//顺时针
while (!pq.empty()) {//遍历到堆为空
pii cur = pq.top();//木桶短板出堆
pq.pop();
for (int k = 0; k < 4; k++) {//顺时针查看周围坐标
int nx = cur.second / n + dirs[k];//转回二维
int ny = cur.second % n + dirs[k + 1];//转回二维
if( nx >= 0 && nx < m && ny >= 0 && ny < n && !visit[nx][ny]) {//限制条件
if (heightMap[nx][ny] < cur.first)//当前高度小于最小高度
res += cur.first - heightMap[nx][ny]; //计算当前柱子蓄水量
visit[nx][ny] = true;//标记防止重复
//关键语句,把更大的值放回堆中(短板只会增大,不会变小)
//把新坐标放回堆中,相当于外圈往内缩
pq.push({max(heightMap[nx][ny], cur.first), nx * n + ny});
}
}
}
return res;
}
};