接雨水
题目:
思路:
贪心:
我们可以按行进行求雨水,整体思路是,首先是求得所有柱子的最高高度,然后从高度为1开始进行遍历(终止条件为大于最高高度),对于每一个高度都需要遍历所有柱子,表示再此高度是否有柱子可以存雨水,在第一次遇到大于等于此时的高度的时候进行进行一次标记,表示从这个柱子开始已经可以进行存雨水了,然后接下来遇到小于此高度的柱子的时候可以进行+1,表示在此高度这个柱子可以存放一个单位的水,此时出现的一个问题是什么时候结束存放雨水,(举个例子:如高度为 3 2 1, 如果此时的高度为3,那么在遇到3的时候进行计数,2进行一次计数,1进行一次计数,如果按照之前的思路来讲的话,高度为3的行会有2个单位的雨水,但是实际情况而言,我们只进行记录了左边界,右边界不知道,也就是在高度为3的情况下,累计的雨水应该为0,因为右边没有任何一个柱子的高度大于等于3,水会流出去)
对于以上情况,我们可以在每一次遇到大于等于此时的高度的时候进行一次刷新(将此时高度已经累计的雨水累加到结果中,并且刷新为0,且将此时的标记置为true,表示可以开始进行记录雨水了),这样在遇到没有右边界的时候也不会将累计的雨水进行累加到结果中去了。
该思路的部分代码实现如下:
int ans = 0;
for(int h = 1;h <= maxhigh; h++){
// 遍历数组中的每一个值
bool start = false; // 用于记录什么时候开始进行累计
int tans = 0;
for(int i = 0;i < numslen;i++){
if(nums[i] < h && start){ // start 用于标记开始进行累加的位置
tans += 1;
}else if(nums[i] >= h){
ans += tans;
tans = 0;
start = true;
}
}
}
动态规划:
由于贪心算法的时间复杂度较高,并不能通过所有测试用例,为此我们需要对算法进行优化。
再次审查题目可知,每一个柱子能存放多少雨水与它左边最高的柱子与右边的最高的柱子有关,然后取左右最高柱子的最小值 减去 这个柱子的高度即可得到该柱子能存放的雨水的量(注意:如果这个差值小于0,那么需要将结果置为0,而不是这个差值)。
有了思路,我们开始实现算法:
找到每一个柱子的左右最大柱子的高度,我们可以设置两个数组,分别用于记录该柱子的左边的最高柱子,以及右边的最高的柱子。
我们设定两个
Left[]
与Right[]
数组,分别记录第i个柱子的左边的高度以及右边的最高的柱子的高度由于柱子具有最优子结构,即第i个柱子的左边最高柱子与第
i - 1
个柱子的左边最高柱子有关,因此我们可以使用动态规划的思想进行求解这个结果,稍加思考,可以想到Left[i] = max(Left[i - 1], hight[i - 1])
,表示第i
个柱子的左边最高高度取值为第i - 1
个柱子的左边最高高度 与 第i - 1
个柱子的最高高度取最大值,这个最大值即为 第i
个柱子的左边的最高高度的柱子的高度。Right[]
的求解与Left[]
类似,只是第i
个柱子的高度与i + 1
个柱子的高度有关。其递推式为:Right[i] = max(Right[i + 1], hight[i + 1])
- 定义边界:由于第
0
个柱子的左边没有柱子,因此可以设定为Left[0] = 0
,并且最后一个柱子的右边没有柱子了,因此可以设定Right[end] = 0
*// 代码实现 left[0] = 0; // 得到每一个柱子的左边最高的柱子的高度 for(int i = 1;i < numslen;i ++){ left[i] = max(left[i - 1], nums[i - 1]); } right[numslen - 1] = 0; // 得到每一个柱子的右边最高的柱子的高度 for(int i = numslen - 1 - 1;i >= 0;i --){ right[i] = max(right[i + 1], nums[i + 1]); }
直接进行一次遍历所有柱子,使用变量
temp
记录第i个柱子的左边的最高高度与右边的最高高度的最小值如果
temp
的值小于第i
个柱子的高度那么将temp
置为0
,否则就累加上num[i] - temp
的值。int ans = 0; int tempMinMax; for(int i = 0;i < numslen;i ++){ tempMinMax = min(left[i], right[i]); // 进行判断,如果tempMinMax的值小于第i个柱子的高度,那么表示这个柱子不可能存有雨水。 ans += tempMinMax - nums[i] < 0 ? 0:tempMinMax - nums[i]; // cout<<ans<<endl; }
代码:
调试理解:
思路一:贪心
// 接水滴
#include<iostream>
using namespace std;
#include<vector>
// 思路一:贪心
int ansT(int nums[], int numslen){
// 首先记录一次最大的高度
int maxhigh = 0;
for(int i = 0;i < numslen;i ++) maxhigh = max(maxhigh, nums[i]);
// 首先遍历高度
int ans = 0;
for(int h = 1;h <= maxhigh; h++){
// 遍历数组中的每一个值
bool start = false; // 用于记录什么时候开始进行累计
int tans = 0;
for(int i = 0;i < numslen;i++){
// cout<<h<<"-"<<nums[i]<<endl;
if(nums[i] < h && start){ // start 用于标记开始进行累加的位置
tans += 1;
}else if(nums[i] >= h){
ans += tans;
tans = 0;
start = true;
}
}
}
return ans;
}
void solve(){
int nums[12] = {0,1,0,2,1,0,1,3,2,1,2,1};
// int nums[6] = {4,2,0,3,2,5};
int numslen = sizeof(nums) / sizeof(nums[0]);
// ans = 6;
cout<<ansT(nums, numslen)<<endl;
return ;
}
int main(){
int t = 1;
while(t--){
solve();
}
return 0;
}
思路二:动态规划
// 接水滴
#include<iostream>
using namespace std;
#include<vector>
// 思路二:动态规划
int ansDP(int nums[], int numslen){
// 首先求出每一个位置的左边的与右边的最高的值
vector<int> left(numslen, -1);
vector<int> right(numslen, -1);
left[0] = 0;
for(int i = 1;i < numslen;i ++){
left[i] = max(left[i - 1], nums[i - 1]);
}
right[numslen - 1] = 0;
for(int i = numslen - 1 - 1;i >= 0;i --){
right[i] = max(right[i + 1], nums[i + 1]);
}
// 一次遍历找出每一个位置的雨水容量
int ans = 0;
int tempMinMax;
for(int i = 0;i < numslen;i ++){
tempMinMax = min(left[i], right[i]);
// 进行判断,如果tempMinMax的值小于第i个柱子的高度,那么表示这个柱子不可能存有雨水。
ans += tempMinMax - nums[i] < 0 ? 0:tempMinMax - nums[i];
// cout<<ans<<endl;
}
return ans;
}
void solve(){
int nums[12] = {0,1,0,2,1,0,1,3,2,1,2,1};
// int nums[6] = {4,2,0,3,2,5};
int numslen = sizeof(nums) / sizeof(nums[0]);
// ans = 6;
cout<<ansDP(nums, numslen)<<endl;
return ;
}
int main(){
int t = 1;
while(t--){
solve();
}
return 0;
}
力扣:
思路一:贪心
时间复杂度: O ( m ⋅ n ) O(m\cdot n) O(m⋅n)
class Solution {
public:
int trap(vector<int>& height) {
int numslen = height.size();
// 记录最大高度
int maxhigh = -1;
for(int i = 0;i < numslen;i ++) maxhigh = max(maxhigh, height[i]);
int ans = 0;
for(int h = 1;h <= maxhigh; h++){
// 遍历数组中的每一个值
bool start = false; // 用于记录什么时候开始进行累计
int tans = 0;
for(int i = 0;i < numslen;i++){
if(height[i] < h && start){
tans ++;
}else if(height[i] >= h){
ans += tans;
tans = 0;
start = true;
}
}
}
return ans;
}
};
class Solution {
public:
int trap(vector<int>& height) {
int numslen = height.size();
// 记录最大高度
int maxhigh = -1;
for(int i = 0;i < numslen;i ++) maxhigh = max(maxhigh, height[i]);
int ans = 0;
for(int h = 1;h <= maxhigh; h++){
// 遍历数组中的每一个值
bool start = false; // 用于记录什么时候开始进行累计
int tans = 0;
for(int i = 0;i < numslen;i++){
if(height[i] < h && start){
tans ++;
}else if(height[i] >= h){
ans += tans;
tans = 0;
start = true;
}
}
}
return ans;
}
};
思路二:动态规划
时间复杂度: O ( n ) O(n) O(n)
class Solution {
public:
int trap(vector<int>& height) {
int numslen = height.size();
// 首先求出每一个位置的左边的与右边的最高的值
vector<int> left(numslen, -1);
vector<int> right(numslen, -1);
left[0] = 0;
for(int i = 1;i < numslen;i ++){
left[i] = max(left[i - 1], height[i - 1]);
}
right[numslen - 1] = 0;
for(int i = numslen - 1 - 1;i >= 0;i --){
right[i] = max(right[i + 1], height[i + 1]);
}
// 一次遍历找出每一个位置的雨水容量
int ans = 0;
int tempMinMax;
for(int i = 0;i < numslen;i ++){
tempMinMax = min(left[i], right[i]);
ans += tempMinMax - height[i] < 0 ? 0:tempMinMax - height[i];
}
return ans;
}
};