【LeetCode】接雨水(dp或双指针)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
分析:
解法1:暴力法
对每个元素取其左右两边的最大值的最小值,然后减去当前值
时间复杂度:O(N^2),对每个元素都需要取其左右两边的最大值
空间复杂度:O(1)
class Solution { public: int trap(vector<int>& v) { int ans=0; if(v.size()==0) return 0; for(int i=1; i<v.size()-1; i++) { int leftmax=0; for(int j1=i-1; j1>=0; j1--) //取其左边元素的最大值 { leftmax=max(leftmax,v[j1]); } int rightmax=0; for(int j2=i+1; j2<v.size(); j2++) //取其右边元素的最大值 { rightmax=max(rightmax,v[j2]); } int x=min(leftmax,rightmax)-v[i];//取两个最大值的最小值 if(x>0)//能够储水 { ans+=x;//符合要求则加上 } } return ans; } }
解法2:动态规划
暴力法每次都有寻找其左右两边最大元素的最小值
我们可以通过两次O(N)的遍历记录下当前元素的左右两边最大值的最小值
时间复杂度:O(N)
空间复杂度:O(N)
class Solution { public: int trap(vector<int>& v) { int ans=0; if(v.size()==0) return 0; int n=v.size(); int dp[n];//dp[i]:当前元素i左右两边最大值的最小值 dp[0]=0; dp[n-1]=0; for(int i=1; i<n-1; i++) //左边最大值 { dp[i]=max(dp[i-1],v[i-1]); } for(int i=n-2; i>=1; i--) //右边最大值和当前左边最小值比较 { dp[i]=min(dp[i],max(dp[i+1],v[i+1])); } for(int i=1; i<n-1; i++) //符合要求累加即可 { if(dp[i]-v[i]>0) ans+=dp[i]-v[i]; } return ans; } }
解法3:左右双指针法
对左半部分的当前值来说:
如果当前值大于当前值左边的最大值,那么水就会向左边流走,当前值就储存不了水,只能更新一下左边最大值
如果当前值不大于当前值左边的最大值,那么就可以储存住水,水的量就是当前左边最大值减去当前值
对右半部分的当前值来说:
如果当前值小于当前值右边的最大值,那么水就会向右边流走,当前值就储存不了水,只能更新一下右边最大值
如果当前值不大于当前值右边的最大值,那么就可以储存住水,水的量就是当前右边最大值减去当前值
注意,左边最大值和右边最大值都不是相对于整个左边部分或右边部分来说的
左边最大值是相当于当前元素的左边的所有元素来说的
右边最大值是相当于当前元素的右边的所有元素来说的
时间复杂度:O(N)
空间复杂度:O(1)
class Solution { public: int trap(vector<int>& v) { int ans=0; int n=v.size(); if(n==0) return 0; int k=0; for(int i=1; i<n; i++) //找到最高点k,k把数组分为左右两部分(都不包含k) { if(v[i]>v[k]) k=i; } int maxleft=0;//左边最大值指针 for(int i=1; i<k; i++) //左半部分 { if(v[maxleft]<v[i])//不能储水,水向左边流走了 maxleft=i;//更新左边最大值 else ans+=(v[maxleft]-v[i]);//可以储存水 } int maxright=n-1;//右边最大值指针 for(int i=n-2; i>k; i--) //右半部分 { if(v[maxright]<v[i])//不能储存水。水向右边流走了 maxright=i;//更新右边最大值 else ans+=(v[maxright]-v[i]);//可以储存水 } return ans; } }
心之所向,素履以往