题目大意:
题解:
动态规划
对于下标\(i\),水能到达的最大高度等于下标\(i\)两边的最大高度的最小值,所以可以用动态规划递推出两边的高度最大值,然后遍历一遍数组,对于下标\(i\)处水能到达的最大高度就等于下标\(i\)两边的最大高度的最小值减去\(height[i]\)。
状态转移方程:
class Solution {
public:
// 动态规划 计算两边的高度最大值
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) {
return 0;
}
vector<int> leftMaxHeight(n);
leftMaxHeight[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMaxHeight[i] = max(leftMaxHeight[i - 1], height[i]);
}
vector<int> rightMaxHeight(n);
rightMaxHeight[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) {
rightMaxHeight[i] = max(rightMaxHeight[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += min(leftMaxHeight[i], rightMaxHeight[i]) - height[i];
}
return ans;
}
};
单调栈
维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组\(height\)中的元素递减。
从左到右遍历数组,遍历到下标\(i\)时,如果栈内至少有两个元素,记栈顶元素为\(top\),\(top\)的下面一个元素是\(left\),则一定有\(height[left] \geq height[top]\)。如果\(height[i]>height[top]\),则得到一个可以接雨水的区域,该区域的宽度是\(i−left−1\),高度是\(min(height[left],height[i])−height[top]\),根据宽度和高度即可计算得到该区域能接的水的量。
在对下标\(i\)处计算能接水的量之后,将\(i\)入栈,继续遍历后面的下标,计算能接的水的量。
class Solution {
public:
// 单调栈 存下标,一层一层注水
int trap(vector<int>& height) {
int ans = 0, n = height.size();
stack<int> st;
for (int i = 0; i < n; ++i) {
while (!st.empty() && height[i] > height[st.top()]) {
int top = st.top();
st.pop();
if (st.empty()) {
break;
}
int left = st.top();
int h = min(height[left], height[i]) - height[top];
int l = i - left - 1;
ans += l * h;
}
st.push(i);
}
return ans;
}
};
尺取法
参考自LeedCode官方题解
动态规划的做法中,需要维护两个数组\(leftMaxHeight\)和\(rightMaxHeight\),因此空间复杂度是\(O(n)\)。是否可以将空间复杂度降到\(O(1)\)?
注意到下标\(i\)处能接的水的量由\(leftMaxHeight[i]\)和\(rightMaxHeight[i]\)中的最小值决定。由于数组\(leftMaxHeight\)是从左往右计算,数组\(rightMaxHeight\)是从右往左计算,因此可以使用双指针和两个变量代替两个数组。
维护两个指针\(left\)和\(right\),以及两个变量\(leftMax\)和\(rightMax\),初始时\(left=0,right=n−1,leftMax=0,rightMax=0\)。指针\(left\)只会向右移动,指针\(right\)只会向左移动,在移动指针的过程中维护两个变量\(leftMax\)和\(rightMax\)的值。
当两个指针没有相遇时,进行如下操作:
- 使用\(height[left]\)和\(height[right]\)的值更新\(leftMax\)和\(rightMax\)的值;
- 如果\(height[left]<height[right]\),则必有\(leftMax<rightMax\),下标\(left\)处能接的水的量等于\(leftMax−height[left]\),将下标\(left\)处能接水的量加到能接水的总量,然后将\(left\)加\(1\)(即向右移动一位);
- 如果\(height[left]\geq height[right]\),则必有\(leftMax \geq rightMax\),下标\(right\)处能接的水的量等于\(rightMax−height[right]\),将下标\(right\)处能接水的量加到能接水的总量,然后将\(right\)减\(1\)(即向左移动一位)。
当两个指针相遇时,即可得到能接的水的总量。
class Solution {
public:
// 双指针
int trap(vector<int>& height) {
int ans = 0, left = 0, right = height.size() - 1, leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
left++;
} else {
ans += rightMax - height[right];
right--;
}
}
return ans;
}
};