Container With Most Water
题目意思:给定一个非负的整数数组,a1,a2,a3,a4.....,an,其中每一个数都代表一个坐标为(i,ai)的点。现在每个点垂直与x轴相交,交点为(i,0),这样就构成了n条垂直线。现在从n条线里面挑出两条,然后和x轴围城了一个矩形问围成的最大的矩形的面积是多少。
难度:中等
此题很容易想到O(n^2)的解法,围成的所有可能的矩形数量为n^2个,所以从这些矩形里面挑出面积最大的即可。所以算法复杂度为O(n^2)。然而如果这样解,就远远超出了限制的时间范围了,这道题只有O(n)的时间复杂的才能Accept。这里介绍两种算法,一种是优化了的O(n^2)算法,一种是O(n)算法
1、 O(n^2)解法
不难想到对于每一条直线而言,它所能围成的最大面积是和离他最远的一条长度不短于它自身的直线围成的面积取的最大,下面给与证明过程:
I.对于最短一条直线i,它所能围成的最大面积为Si=max|x-i|*ai (ax >= ai),当|x-i|取得最大值时S有最大值
Ii. 对于第二短的直线j,那麼它所能围成的面积为Sj=max{max|x-j|*aj, |i-j|*ai} (ax>= aj && aj >= ai),即在它自身和比它长的直线取得最大值和与它短的直线围成的面积两者之间取最大
Iii.对于第三短的直线k,同理Sk=max{max|x-k|*ak, |k-j|*aj,|k-i|*ai} (ai<=aj<=ak=ax)
以次类推,对于任意一条直线Kn,有SKn=max{max|x-Kn|*aKn, |Kn-Kn-1|*aKn-1, |Kn-Kn-2|*aKn-2, |Kn-Kn-3|*aKn-3.....,|Kn-K1|*aK1}。
由于,max|x-Ki|*aKi >= |Kn-Ki|*aKi 所以Ski >= |Kn-Ki|*aKi
所以由于在已经计算了SK1~SKn-1,计算SKn时只需要计算max|x-Kn|*aKn,最大值只可能在{SK1,SK2,SK3,SK4....,SKn-1,max|x-Kn|*aKn}某一项取得最大值,即只需要计算aKn和距离它最远的不短于它的直线所围成的面积即可。下面是代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int isize = height.size();
//std::cout<<isize<<std::endl;
int i, l, r, iResult = -1;
int iTemp = 0;
int w, h;
for(i = 1; i <= isize; i ++)
{
l = 1;
r = isize;
w = (i - 1) > (isize - i) ? (i - 1) : (isize - i);
if(height[i - 1] * w <= iResult)//优化1
continue;
while(1)
{
if(l < i && l >= 1 && height[l - 1] < height[i - 1])
{
l ++;
}
else
{
break;
}
}
while(1)
{
if(r > i && r <= isize && height[r - 1] < height[i - 1])
{
r --;
if(r - i <= i - l)//优化2
{
r = i;
break;
}
}
else
{
break;
}
}
if(l == i && r == i)
continue;
if(l == i)
w = r - i;
else if(r == i)
{
w = i - l;
}
else w = r - i;
iTemp = w * height[i - 1];
iResult = iResult > iTemp ? iResult : iTemp;
//std::cout<<iResult<<" "<<i<<std::endl;
}
return iResult;
}
};
对于15000个数据的测试用例需要运行500ms,所以费了老劲优化最终还是会超时==
下面介绍一种O(n)的解法
2、 O(n)
这道题可以从两边开始逐渐向中间靠近的方法计算最大值,也就是所谓的两点法:维护两个变量l,r分别从左右两边开始逐渐向中间靠拢,当height[l] < height[r] 则l++,如果height[l]==height[r] 则l++,r--;如果height[l] > height[r]则r--通俗的概括谁小谁向中间靠拢即Slr=(r - l)*min{height[l],height[r]},遍历取其中最大值即可所以算法复杂度为O(n^2),下面给出这种解法的正确性的证明:
对于任意的两条直线i,j (j > i)有三种情况ai < aj, ai=aj, ai>aj
(1)ai < aj
在这个前提下按照算法规则i++,j保持不变。面积Sij=(j - i) * ai,即对于直线i只需要计算其与aj的所围成的面积即可,而不需要枚举它和每条先围成的面积。你也许有疑问:也许存在一条直线k(k>j),Sik围成的面积才是直线i所能围成的最大面积,也就是说不应该只计算了Sij就把i++。假设存在直线k使得Sik为i直线所能围成的最大面积,那麼根据算法规则在直线左边肯定存在一条直线x,满足ax>ak且x<=i,那麼一定有Sxk =(k-x)*ak >= Sik=(k-i)*min{ai,ak} 因为(k-x) >= (k-i)且ak>=min{ai, ak}。根据规则Sxk在Sij计算之前一定已经计算过了,所以也就没必要再枚举i,k的情况故而i++没有错。
(2) 同理可以证明ai==aj以及aj < ai的情况,都只需要计算当前Sij即可。
下面是代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int isize = height.size();
//std::cout<<isize<<std::endl;
int i, l, r, iResult = -1;
int w, h;
l = 0;
r = isize - 1;
//从两边向中间靠拢
while(l < r)
{
h = height[l] < height[r] ? height[l] : height[r];
iResult = iResult > (r - l) * h ? iResult : (r - l) * h;//计算出较短的直线所能围成的最大面积
if(height[l] < height[r])
l ++;
else if(height[l] > height[r])
r --;
else
{
l++;
r --;
}
}
return iResult;
}
};