接雨水问题
问题描述:给定一个整形数组arr,已知其中所有的值都是非负的,将这个数组看作一个柱子高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例:
输入:[3,1,2,5,2,4]
返回值:5
说明:数组 [3,1,2,5,2,4] 表示柱子高度图,在这种情况下,可以接 5个单位的雨水,蓝色的为雨水 ,使用图形解释如下:
方法一
思路分析
相信每个人都知道木桶原理,就是说一只木桶能盛多少水并不取决于桶壁上最长的那块木板,而恰恰取决于最短的那块。通过类比发现,每一根柱子所能承受的雨水=其左侧最高柱子与右侧最高柱子中的较小值-其自身柱子高度。因此最直接的办法是通过循环遍历数组arr,例如arr[i],找到数组中arr[i]左侧最大值与右侧最大值中的较小值m,则第i个柱子所能承受的雨水=m-arr[i],最后将所有的结果相加,然后这种方法由于存在双层循环,时间复杂度大大提升。在每一次寻找arr[i]的左侧最大值与右侧最大值,可能会存在重复寻找的问题,因此我们可以想到定义二维数组将arr每一个变量左右两侧的最大值保存,从而减少计算。步骤如下:
• 定义二维数组$dp[n][2]$,其中$dp[i][0]$用于保存arr[i]左侧柱子的最大值,$dp[i][1]$用于保存arr[i]右侧柱子的最大值。
• 寻找arr[i]左侧柱子的最大值可以直接使用arr[i-1]左侧柱子的最大值,其值已经保存在$dp[i-1][0]$中,从而减少循环计算
• 寻找arr[i]右侧柱子的最大值可以直接使用arr[i+1]右侧柱子的最大值,其值已经保存在$dp[i+1][0]$中,减少循环计算
• 每一个柱子的雨水量=其左侧最高柱子与右侧最高柱子中的较小值-其自身柱子高度
实例分析
给定数组arr[6]={3,1,2,5,2,4}
| arr[i] | 3 | 1 | 2 | 5 | 2 | 4 |
| 左侧最大值$dp[i][0]$ | 3 | 3 | 3 | 5 | 5 | 5 |
| 右侧最大值$dp[i][1]$ | 5 | 5 | 5 | 5 | 4 | 4 |
| 雨水为$min(dp[i][0],dp[i][1])-arr[i]$ | 0 | 2 | 1 | 0 | 2 | 0 |
| 总的雨水 | 5 | |||||
C++核心代码
class Solution {
public:
/**
* max water
* @param arr int整型vector the array
* @return long长整型
*/
long long maxWater(vector<int>& arr) {
// write code here
long n = arr.size();
if (n == 0)
{
return 0;
}
long res = 0;
vector<vector<int> > dp(n); //定义二维数组,其中dp[i][0]用于保存arr[i]左侧柱子的最大值,dp[i][1]用于保存arr[i]右侧柱子的最大值。
for(int i=0;i<n;i++)
{
dp[i].resize(2);
}
dp[0][0] = arr[0];//左侧第一个柱子的左侧柱子最大值为本身
dp[n-1][1] = arr[n-1];//右侧第一个柱子的右侧柱子最大值为本身
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], arr[i]);//寻找arr[i]左侧柱子的最大值可以直接使用arr[i-1]左侧柱子的最大值,其值已经保存在dp[i-1][0]中
}
for (int i = n - 2; i >= 0; i--)
{
dp[i][1] = max(dp[i + 1][1], arr[i]);//寻找arr[i]右侧柱子的最大值可以直接使用arr[i+1]右侧柱子的最大值,其值已经保存在dp[i+1][0]中
}
for (int i = 0; i < n; i++)
{
res += min(dp[i][0], dp[i][1]) - arr[i];//每一个柱子的雨水量=其左侧最高柱子与右侧最高柱子中的较小值-其自身柱子高度
}
return res;
}
int max(int a,int b)
{
return (a>b?a:b);
}
int min(int a,int b)
{
return (a>b?b:a);
}
}; 复杂度分析
• 时间复杂度:代码中使用四次单层循环遍历,,因此时间复杂度为$T(n)=O(3*n)$,总的时间复杂度为$O(n)$。
• 空间复杂度:使用了一个n×2的二维数组,因此空间复杂度为$O(n)$。
方法二
思路分析
在上面的方法中,在计算arr[i]左侧柱子最大值与右侧柱子最大值时,使用了两次循环,即从左到右的循环和从右到左的循环,由此我们可以想到链表中的双指针,即通过设置双指针的办法,通过指针的移动,从而计算每个柱子可存储的水量,直到两个指针相遇。具体步骤如下:
• 初始化左侧柱子最大值left_max=arr[0],右侧柱子最大值right_max=arr[n-1]
• 设置左侧指针为left,初始化为0,指向第一个柱子,向右移动
• 设置右侧指针为right,初始化为n-1,指向最后一个柱子向左移动
• 两个指针还未相遇时:
• 如果左侧柱子最大值left_max小于右侧柱子最大值,并且left_max大于arr[left],则可以计算当前左侧柱子的雨水,即为left_max-arr[left],否则将更新left_max的值为arr[left]。之后left向右侧移动
• 如果左侧柱子最大值left_max大于右侧柱子最大值,并且right_max大于arr[right],则可以计算当前右侧柱子的雨水,即为right_max-arr[right],否则将更新right_max的值为arr[right]。之后right向左侧移动
• 当两个指针相遇时即为运行结束的条件,此时左右柱子的雨水都已计算完。
实例分析如图所示
JAVA核心代码
import java.util.*;
public class Solution {
/**
* max water
* @param arr int整型一维数组 the array
* @return long长整型
*/
public long maxWater (int[] arr) {
// write code here
int n = arr.length;
if(n == 0)
{
return 0;
}
long res = 0;
int left = 0, right = n - 1;
int left_max = arr[0], right_max =arr[n-1];
while(left <= right)
{
if (left_max < right_max)//左侧柱子最大值left_max小于右侧柱子最大值
{
if (left_max > arr[left])//left_max大于arr[left]
{
res +=left_max-arr[left];//计算当前左侧柱子的雨水
}
else
left_max = arr[left];
left++;
}
else//左侧柱子最大值left_max大于右侧柱子最大值
{
if (right_max > arr[right])//right_max大于arr[right]
{
res+=right_max-arr[right];//计算当前右侧柱子的雨水
}
else
{
right_max=arr[right];
}
right--;
}
}
return res;
}
} 复杂度分析
• 时间复杂度:单层循环,时间复杂度为$O(n)$
• 空间复杂度: 使用了几个变量作为指针,因此空间复杂度为$O(1)$

京公网安备 11010502036488号