接雨水
这道题我们怎么做呢?
假设每个位置都有宽度为1的桶,计算接水多少需要知道左边木板的高度和右边木板的高度,这两个取最小值,左边木板的高度取决于左边的最大高度(高流低不流),右边木板的高度取决于右边的最大高度,那么具体应该怎么实现呢?
方案一:前后缀分解
使用两个数组,第一个数组存储最左边到第i个位置的最大高度(前缀最大值:上一个最大值和高度取最大值)
第二个数组存储从最右边到第i个位置的最大高度(后缀最大值)
最后我们分别遍历前缀最大值和后缀最大值和当前高度,使用后缀最大值和前缀最大值的差减去当前高度即为可接水的高度,把每个水桶能接的水算出来,相加即为答案
时间复杂度:O(N)
int trap(int* height, int heightSize)
{
int n=0;
int pre[20000]={0};
int last[20000]={0};
pre[0]=height[0];
for(int i=1;i<heightSize;i++)
{
pre[i]=fmax(height[i],pre[i-1]);
}
last[heightSize-1]=height[heightSize-1];
for(int i=heightSize-2;i>=0;i--)
{
last[i]=fmax(height[i],last[i+1]);
}
for(int i=0;i<heightSize;i++)
{
n+=fmin(pre[i],last[i])-height[i];
}
return n;
}
方案二:相向双指针
第一种方法在时间上已是最优,那么能不能在空间上进行优化呢?
假设我们已经计算出了一部分前缀的最大值和一部分后缀的最大值,如果前缀最大值比后缀最大值小,那么左边这个木桶的容量就是前缀最大值,算完之后将其向右扩展;反之,若后缀最大值小于前缀最大值,那么右面木桶的容量就是后缀最大值,算完之后将其向左扩展。
怎么用代码实现呢?
用两个指针
不需要创建额外数组,空间复杂度为O(1)
代码:
int trap(int* height, int heightSize)
{
int n=0;
int left=0;
int right=heightSize-1;
int pre_max=0; //前缀最大值
int suf_max=0; //后缀最大值
while(left<=right)
{
pre_max=fmax(pre_max,height[left]);
suf_max=fmax(suf_max,height[right]);
if(pre_max<suf_max)
{
n+=pre_max-height[left];
left++;
}
else
{
n+=suf_max-height[right];
right--;
}
}
return n;
}
盛水最多的容器
这道题给了我n条线,我需要从中选择两条构成一个容器,容器的高度取决于短线,容器的宽度取决于这两线的距离(下标差),我们随便选择两条线,容纳的水即为图中蓝色面积,短的线和中间的线构成容器,则有两种情况:
1.中间的线比它短:容纳的水宽度、高度都变少,面积肯定也变少
2.中间的线比它长(或一样长):容纳的水宽度变少,高度不变,面积也变少
所以得出结论:中间的任何线都无法和它构成一个容量更大的容器,即若要找到比蓝色区域更大的容器,则肯定不包含那条线(直接去掉、在剩下的线中继续找)
做法:初始化两个指针:
l=0;
r=n-1;
哪条短就移动哪条,若两条线一样长,则移动哪个都可以 ,移动前先算出这两条线的面积,如果比答案大就更新答案
时间复杂度:O(N)
实现代码:
int maxArea(int* height, int heightSize)
{
int left=0;
int n=0;
int right=heightSize-1;
while(left<right)
{
int area=(right-left)*fmin(height[left],height[right]);
n=fmax(n,area);
height[left]<height[right]?left++:right--;
}
return n;
}