题目出处
给字n个非负整数, a1,a2, … , an, 对应着坐标轴(i, ai), 对每个数连接x轴画一条线段,端点 (i, ai) 和 (i, 0).
对两个数,连接端点, 再与x轴形成长方形, 求长方形的面积就想关于盛水的体积, 求最大的盛水量。
如假设给定的10个数:
[5, 4, 3, 10, 2, 7, 8, 6, 1, 9],即10 个点 (0, 5), (1, 4), (2, 3), (3, 10), … ,(9, 9) 对应的直观图如下
蓝色区域代码的是 (0, 5),(3, 10)两点的盛水量为: 5*3 = 15.
相应的, 灰色区域代码的是 (3 10),(5, 7)两点的盛水量为: 7*(5 -3) = 14.
相应的, 黄色区域代码的是 (6, 8),(9, 9)两点的盛水量为: 8*(9 -6) = 24.
上图数据中,最大值为(3, 10) , (9, 9), 盛水量为: 9 * (9-3) = 54
分析
最直接的求法是计算两两计算一遍,求最大值。
但是明显的,有很多没必要的运算. 比如上图中的(1, 4), (2, 3) 与(10, 10) 的计算。
由于这是计算盛水量,所以主要取决于较小的数据,所以最大的数据再大,也没有起作用。
考虑到一般情况:
两个数字(i, ai), (j, aj), 他们的盛水量为 area =(j-i)*min(ai, aj), 取(k, ak) 使 i < k < j, 使面积比area更大。
不失一般性,设 ai <= aj,则min(ai, aj) = ai.
当ak <= ai 时, 由于 (k-i) * ak < area 和 (j-k) * ak < area(由于 k-i < j-i 和 j-k < j-i), 所以这种情况得到的结果肯定小于 area. 可以不考虑。
当 ak > ai 时
ak 与ai的盛水量: (k-i)*ai < area, 所以这种情况也不需要考虑。
ak与aj的盛水量: (j-k)*min(ak, aj) 由于 j-k < j-i 而 min(ak, aj) > ai, 不确定是否大于area.
所以可得到简单算法:
- 取第一和最后的值为初始值,计算最近 盛水量。
- 往中间移动较小值的指针,录找比它大的数。
- 再计算盛水量,并比较大小。
- 重复2, 3.
算法代码
int maxArea(vector<int>& height) {
int first = 0, end = height.size()-1;
int min = height[first] > height[end] ? end : first;
int maxArea = (end-first)*height[min];
while(first < end){
// cout<<first << " " << end << " min:" << min << endl;
// 移动最小值到下一个比当前最小值大的数。
if(first == min){
while(first < end && height[first] <= height[min]) first += 1;
}
if(end == min){
while(first < end && height[end] <= height[min]) end -= 1;
}
// 没有就退出
if(first >= end ) break;
// 计算盛水量比较
min = height[first] < height[end] ? first : end;
int area = (end-first) * height[min];
if(area > maxArea)
maxArea = area;
}
return maxArea;
}