2021年03月13日 周六 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】
本文目录
1. 问题简介
2. 多种解法(数学法最为巧妙)
2.1 按列求(循序渐进)
2.1.1 暴力法(双循环)
暴力法:对数组进行遍历,每次都用循环计算左右两边的边界值,然后获得这一列的水量。
暴力法虽然时间复杂度高,为O(n*n)
,但是理解暴力法后,就可以对其进行改进,从而得到更好的方法。
class Solution {
public:
int trap(vector<int>& height) {
const int n = height.size();
int res = 0;
for(int i=1;i<n-1;++i){
int max_left = 0, max_right = 0;
for(int j=i;j>=0;--j)
max_left = max(max_left,height[j]);
for(int j=i;j<n;++j)
max_right = max(max_right,height[j]);
res += min(max_left,max_right)-height[i];
}
return res;
}
};
2.1.2 暴力法优化一——动态规划
暴力法在计算左右两边的边界值时,存在大量的重复计算,所以可以用动态规划存储中间结果,将时间复杂度降为O(n)
。
class Solution {
public:
int trap(vector<int>& height) {
if(height.empty()) return 0;
const int n = height.size();
int ans = 0;
vector<int> max_left(n), max_right(n);
max_left[0] = height[0];
max_right[n-1] = height[n-1];
for(int i=1;i<n;++i)
max_left[i] = max(max_left[i-1],height[i]);
for(int i=n-2;i>=0;--i)
max_right[i] = max(max_right[i+1],height[i]);
for(int i=0;i<n;++i)
ans += min(max_left[i],max_right[i])-height[i];
return ans;
}
};
2.1.3 暴力法优化二——双指针,巧妙求解
双指针法也是暴力法的改进,其突出一个左右横跳
,左右两边哪边高就去遍历另一边,计算正在遍历的列的存水量,这样一趟遍历下来,结果也就出来了,并且只需要常数空间。
class Solution {
public:
int trap(vector<int>& height) {
if(height.empty()) return 0;
int i = 0, j = height.size()-1;
int left_max = height[i], right_max = height[j];
int ans = 0;
while(i<j){
if(height[i]<=height[j]){
++i;
left_max = max(left_max,height[i]);
if(height[i]<left_max)
ans += left_max-height[i];
}
else {
--j;
right_max = max(right_max,height[j]);
if(height[j]<right_max)
ans += right_max-height[j];
}
}
return ans;
}
};
2.2 按行求(单调栈)
单调栈是按行来求解,思路比较难理解一些,总体原则是:
-
当前高度小于等于栈顶高度时,入栈,指针后移。
-
当前高度大于栈顶高度时,出栈,然后计算当前栈顶和当前高度所围成区域的行储水量,直到当前墙的高度小于等于栈顶高度或者栈空,然后把当前墙入栈,指针后移。
class Solution {
public:
int trap(vector<int>& height) {
const int n = height.size();
stack<int> st;
int res = 0, cur = 0;
while(cur<n){
while(!st.empty() && height[st.top()]<height[cur]){
int top = height[st.top()];
st.pop();
if(st.empty()) break;
int d = cur-st.top()-1;
int h = min(height[cur],height[st.top()])-top;
res += d*h;
}
st.push(cur++);
}
return res;
}
};
2.3 数学法(韦恩图,拍案叫绝)
这种方法是无意间在leetcode题解上看到的:https://leetcode-cn.com/problems/trapping-rain-water/solution/wei-en-tu-jie-fa-zui-jian-dan-yi-dong-10xing-jie-j/,利用韦恩图来求解,可谓是脑洞大开了,并且也只需要常数的空间复杂度。
s_right:
假设最右边是封闭的,能接到的雨水量
s_left:
假设最左边是封闭的,能接到的雨水量
s_rect:
假设两边都是封闭的,能接到的雨水量,也就是整个矩形的面积
s_zhu :
柱子的面积
由上图阴影区域可知:s_left + s_right - s_rect = s_zhu + res
class Solution {
public:
int trap(vector<int>& height) {
if(height.empty()) return 0;
const int n = height.size();
int left_max = height[0], right_max = height[n-1];
// s_left:假设最左边是封闭的,能接到的雨水量
// s_right:假设最右边是封闭的,能接到的雨水量
// s_rect:假设两边都是封闭的,能接到的雨水量,也就是矩形的面积
// s_left + s_right - s_rect = s_zhu + res
int s_left = 0, s_right = 0, s_zhu = 0, h_max = height[0];
for(int i=0;i<height.size();++i){
left_max = max(left_max,height[i]);
s_left += left_max;
right_max = max(right_max,height[n-1-i]);
s_right += right_max;
h_max = max(h_max,height[i]);
s_zhu += height[i];
}
int s_rect = h_max*n;
return s_left + s_right - s_zhu - s_rect;
}
};
参考文献
https://leetcode-cn.com/problems/trapping-rain-water/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-8/
https://leetcode-cn.com/problems/trapping-rain-water/solution/wei-en-tu-jie-fa-zui-jian-dan-yi-dong-10xing-jie-j/