目录
题目描述:
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
方法一(自己写的方法):
class Solution {
public:
int trap(vector<int>& height) {
stack<pair<int, int>> sta; //存储柱高和对应的下标,栈中的数值从栈底到栈顶为降序
int minlevel = INT_MAX; //当前最低的水平面的高度
int ans = 0;
for (int i = 0; i < height.size(); i++)
{
int cur = height[i];
//当栈为空,或者当前的柱高小于栈顶时,直接入栈
if (sta.empty() || sta.top().first > cur)
{
sta.emplace(cur, i);
minlevel = min(minlevel, cur); //随时更新最低的水平面高度
}
else
{
//当栈顶小于等于当前的柱高时,不断计算和弹栈,直至栈为空或者栈顶大于当前的柱高
while (!sta.empty() && sta.top().first <= cur)
{
ans += (min(sta.top().first, cur) - minlevel) * (i - sta.top().second - 1);
minlevel = min(sta.top().first, cur); //随时更新最低的水平面高度
sta.pop();
}
if (!sta.empty()) //栈中还剩的第一个比cur大的值,也要计算一下
{
ans += (min(sta.top().first, cur) - minlevel) * (i - sta.top().second - 1);
minlevel = min(sta.top().first, cur); //随时更新最低的水平面高度
}
sta.emplace(cur, i); //最后将cur入栈
minlevel = cur; //最终的最低的水平面高度就是cur的高度
}
}
return ans;
}
};
大致思想为:
利用一个栈来实现,栈中的数值从栈底到栈顶为降序排放,每次遍历到一个新的柱高,就和栈顶的柱子高度比较,小于就直接入栈(显然,如果柱子的高度越来越小,那是不可能接到水的),大于等于就计算一下以当前的柱子和栈顶的柱子作为容器能接多少水,然后弹栈继续遍历,最终将当前的柱子也入栈。
实现中还存在的其他细节已写在注释中,例如还需要一个重要的参数:minlevel,记录当前的最低水平面,类比于容器的底部厚度,水需要从哪个高度开始存放。
方法二(单调栈):
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
stack<int> sta; //存储下标,栈中各个下标对应的柱高从栈底到栈顶降序
for (int i = 0; i < height.size(); i++)
{
int right = height[i];
//如果栈不为空,并且栈顶所对应的柱高小于当前的柱高
while (!sta.empty() && height[sta.top()] < right)
{
int minlevel = height[sta.top()];
sta.pop();
//如果原栈中只有一个元素,那就不可能构成一个容器接到雨水,直接退出
if (sta.empty())
{
break;
}
int left = height[sta.top()];
//left作为容器的左边高度,right作为容器的右边高度,minlevel作为容器的底部厚度,i - sta.top() - 1作为容器的宽度
ans += (min(left, right) - minlevel) * (i - sta.top() - 1);
}
//最终将当前的柱子下标入栈
sta.push(i);
}
return ans;
}
};
看了下题解,嗯果然比我写的好懂多了,代码简洁好看而且还效率更高。
该方法的思想很简单,同样是用一个单调栈,栈中记录各个柱子的下标,栈底到栈顶的下标所对应的柱高是降序的。于是每遇到一个柱子,如果小于等于栈顶的柱子高度就直接入栈(和方法一讲述的一样),如果大于栈顶的柱子高度,就从栈中选出栈顶和次栈顶的元素,次栈顶的柱子肯定比栈顶的柱子高,于是次栈顶的柱子和当前的柱子作为容器的左右两边、栈顶的柱子作为容器的底部厚度,即可算出能够接到的雨水体积。
方法三(动态规划):
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
int n = height.size();
vector<int> pre(n); //前缀最高
vector<int> rea(n); //后缀最高
pre[0] = height[0];
for (int i = 1; i < n; i++)
{
pre[i] = max(pre[i - 1], height[i - 1]);
}
rea[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--)
{
rea[i] = max(rea[i + 1], height[i + 1]);
}
for (int i = 1; i < n - 1; i++)
{
int temp = min(pre[i], rea[i]) - height[i];
//若小于等于0,说明当前柱子上不能接雨水
if (temp > 0)
{
ans += temp;
}
}
return ans;
}
};
一种更加简单而且效率更高的方法,居然没能想到。
该方法的思路更简单,说是动态规划,实际上就是利用前缀数组和后缀数组求解。对于每个柱子,其上方能够接到的雨水为,min(左边的最大高度,右边的最大高度) - 柱子本身的高度。
于是利用两个数组,计算出柱子i的前缀最高柱子和后缀最高柱子,即可求解。
方法四(双指针法):
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
int n = height.size();
int left = 0; //左指针
int leftMax = height[0]; //随时更新左边的最大值
int right = n - 1; //右指针
int rightMax = height[n - 1]; //随时更新右边的最大值
while (left <= right)
{
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (leftMax < rightMax) //如果左边的最大值小于右边的最大值,那么当前柱子可以接的雨水高度由leftMax决定
{
ans += leftMax - height[left];
left++;
}
else //如果右边的最大值小于左边的最大值,那么当前柱子可以接的雨水高度由rightMax决定
{
ans += rightMax - height[right];
right--;
}
}
return ans;
}
};
可以看做是方法三(动态规划法)的优化,将前缀和后缀两个数字简化为两个指针和两个记录两边最大值的变量,可以将空间优化到O(1)。
只要leftMax > rightMax,那么柱子height[right]上积水的高度将由rightMax决定,反之亦然。