问题描述:
给你n个非负整数a1, a2, …, an,每个数代表一个柱子的高度,如果两根柱子跟x轴能够构成一个容器,那么用这个容器接水.试问选择哪两根柱子能够使得所接的水最多?
算法思想:
遍历这个高度数组,
首先判断以某根柱子高度为所接水的高度的面积最大者,这个面积所对应的两个柱子(高度).判断过程是这样的:设计两个指针,固定一个指针指向当前的高度,将另一个指针遍历这个高度数组,但是这个指针是倒序遍历的.根据短桶原理,一个木桶所能接的最高水位是不高于最短的那个木板的.那么在这里如果水位要跟其中一个柱子的高度持平,那么另一根柱子的高度一定要大于等于前一个柱子的高度.所以我们倒序遍历,找到第一个高于第一个指针所指柱子高度的柱子,那么这跟柱子与前一根柱子所接的水的面积,就是以第一根柱子高度为水位高度的最大面积.因为当高度固定时,宽度越大,面积越大.但是如果第一根柱子在最左边的话,这个最大时成立的,然而,如果柱子不是在最左边,而是中间靠右的位置的话,这个面积就不一定时最大的,因为可能存在这根柱子左边的柱子,使得面积更大.
让我们再重新捋一下,首先我们遍历这个数组,对每次遍历到的高度,利用双指针方法判断以p1指针所指高度为水位高度的最大面积,而p2指针既要有倒序遍历的情况,也要考虑正序遍历的情况,比较出两个面积中的最大者,保存为当前面积最大者maxArea.下一次用同样的方法得到的maxArea跟前面一个maxArea进行大小比较,将更大的值赋值给maxArea.所以maxArea保存的时当前最大的水的面积.当遍历结束,最终得到的maxArea就是最大的水的面积,它所对应的两个高度也就随即得出.
利用java编写的参考代码如下:
/*
* 给你n个非负整数a1, a2, ..., an,每个数代表坐标中的一个点(i, ai).
* 在坐标内画n条垂直线,垂直线i的两个端点分别为(i, ai)和(i, 0).找出其中的两条线,使得它们与
* x轴共同构成的容器可以容纳最多的水.
* */
public class MaxAreaBetweenTwoPillars {
public static void main(String[] args) {
int[] height = {1, 8, 6, 2, 5, 4, 8, 3, 7};
int maxArea = maxArea(height);
System.out.println(maxArea);
}
public static int maxArea(int[] height){
//最大面积变量,左指针下标,数组height的长度
int maxArea = 0, left = 0, length = height.length;
//也是面积变量,因为需要另一个表示面积的变量用来和当前的最大面积比较
int area;
//右指针下标
int right;
//从前面开始找
while(left < length){
right = length - 1;
while(right > left){
if(height[right] < height[left]){
right--;
}else{
break;
}
}
//计算矩形的面积
area = height[left] * (right - left);
//保存计算过的最大的面积
maxArea = Math.max(maxArea, area);
left++;
}
//上面的遍历完成后,得到的不一定时每个高度所对应的最大面积.
//从后面开始找
right = length - 1;
while(right > 0){
left = 0;
while(right > left){
if(height[right] > height[left]){
left++;
}else{
break;
}
}
area = height[right] * (right - left);
maxArea = Math.max(maxArea, area);
right--;
}
return maxArea;
}
}
这个方法还可以优化.当我们在遍历这个数组的时候,我们每遍历到一个高度,应该要得到这个高度为水位高度的矩形最大面积.但前面提到这需要遍历两次,一次从后往前遍历,一次从前往后遍历.但我们可以只遍历一次.比如我们考虑下面这个图
如果我们left指向第一个柱子,那么right从后往前遍历,第一个right所指的即第6个柱子高度小于第一个柱子,虽然对于left而言,并没有找到比left更高的第一个柱子,但是对right而言却找到了第一个比right更高的柱子,因为此时宽度最大,right后面也不可能有其他柱子,所以这个时候的面积就是right高度的最大面积.因为left还没有找到它所对应的最大面积,就要继续令right–;每当right停在一根柱子上,不是left找到比left更高的柱子就是right找到比right更高的柱子,无论怎样我们都可以得到一个最大的面积值,跟之前的maxArea比较即可,然后哪个柱子还没有找到它所对应的最大面积,就让另一个指针向中间靠拢,直到最后left = right.
public static int maxArea1(int[] height){
int maxArea = 0, left = 0, right = height.length - 1;
int area = 0;
while(left < right){
//计算面积,面积等于宽 * 高, 宽就是left和right之间的距离,
//left 和 right 所对应的最低高度
area = Math.min(height[left], height[right]) * (right - left);
//保存计算过的最大的面积
maxArea = Math.max(maxArea, area);
//柱子矮的往中间靠
if(height[left] < height[right]){
left++;
}else{
right--;
}
}
return maxArea;
}
其结果是一样的.
以上.