您的位置:首页 > 大数据 > 人工智能

LeetCode:Container With Most Water

2013-10-17 18:29 399 查看
/**
Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.

Note: You may not slant the container.

题目大意:二维坐标轴上有n个点[i, ai],每个点画一条x轴的垂线(连接[i, ai]和[i, 0]),选两条线作为盆子装水,找出最多水的方案
**/
import java.util.*;

public class Solution {
public int maxArea(int[] height) {
//return solve1(height);      //超时
//return solve2(height);      //想法错误
//return solve3(height);
return solve4(height);
}

//================================ solve1 ========================================
//O(n^2)
//双循环直接穷举,必然超时
private int solve1(int[] height){
int result = -1;
int index = -1;

for(int i=0; i<height.length; i++){
for(int j=i+1; j<height.length; j++){
index = i;
if(height[i] > height[j])
index = j;

int water = height[index] * (j-i);
if(result < water)
result = water;
}
}

return result;
}

//================================ solve2 ========================================
//想法本身有问题,没能维护两点之间的宽度,算出来的果断是错误答案
//O(n * log2 n) = O(n * log2 n) + O(n)
//先对数组进行排序,然后遍历,对于[i, ai],直接乘以他到最优的距离就可以得出当前点可容纳最多水的数量
private int solve2(int[] height){
Arrays.sort(height);

int result = -1;
int tmp = -1;

for(int i=0; i<height.length; i++){
tmp = height[i] * (height.length - 1 - i);  //注意这里要减1才能得到宽度

if(result < tmp)
result = tmp;
}
return result;
}

//================================ solve3 ========================================
//O(m^2) = O(n) + O(n) + O(m^2)
//搜索左端点 + 搜索右端点 + 查询并获取最大值
/**
考虑从这个题目本身的数学性质出发:
现在指定两点[i, ai],[j, aj],面积f(1)=(j-i)*min(ai, aj),固定左端点,移动右端点:
1、右端点右移,有两种情况:
[j+k, b1],其中b1>=aj,则f(b1)=(j+k-i)*min(ai, b1),明显可得f(b1)>f(1);
[j+k, b2],其中b2<=aj,则f(b2)=(j+k-i)*min(ai, b2),f(b2)和f(1)大小不定;
2、右端点左移,也有两种情况:
[j-k, c1],其中c1>=aj,则f(b1)=(j-k-i)*min(ai, c1),f(c1)和f(1)大小不定;
[j-k, c2],其中c2<=aj,则f(b2)=(j-k-i)*min(ai, c2),明显可得f(c2)<f(1);

左端点的情况和右端点对称,可以得出结论:如果程序从两端开始,向中间收缩,只能选择越来越高的点才能保证容量不变小。

例如,序列是[5(0),4(1),6(2),12(3),8(4),32(5),9(6),1(7),4(8)],
左端点i的选择只能是:0 -> 2 -> 3 -> 5
右端点j的选择只能是:8 -> 6 -> 5
可以从这一点出发,对水盆左右端点的情况进行缩减
**/
private int solve3(int[] height){
int[] leftPoint = new int[height.length];
int leftNum = 0;

int[] rightPoint = new int[height.length];
int rightNum = 0;

//选取左端点集合:
for(int i=0, currentPoint=height[i]; i<height.length; i++){
if(height[i] >= currentPoint){
leftPoint[leftNum++] = i;
currentPoint = height[i];
}
}

//选取右端点集合:
for(int i=height.length-1, currentPoint=height[i]; i>=0; i--){
if(height[i] >= currentPoint){
rightPoint[rightNum++] = i;
currentPoint = height[i];
}
}

//在所得的点里开始计算
//这里可以有一个优化的策略:当一端静止,另一端向其靠近,如果另一端的高度比这一端要高,那就可以结束当前循环了
int result = -1;
for(int i=0; i<leftNum; i++){
for(int j=0; j<rightNum; j++){    //在这里可以加一个条件
int left = leftPoint[i];
int right = rightPoint[j];
if(left >= right)
break;
int width = right - left;

//这里要用矮的那一个作为盆子的高度
int tall = min(height[left], height[right]);

int water = tall * width;
if(result < water)
result = water;
}
}

return result;
}

//================================ solve4 ========================================
//O(n)
//左右靠拢,到中间合并的时候必然可得最大值
/**
现在试试左右两端往中间收窄的策略:
当固定其中一端i,另一端j向其靠近,如果发现 height[j] >= height[i];
那么这个时候j就不需要在靠近了,因为即使在靠近,高度也限制在i点上,无法增加,
此时应固定j点,转由i点向j点靠近。
同时对中间每一步产生的容量值进行计算,得出最大值。

其实使用了solve3的一些结论,在这里只移动矮的那一边,因为移动高的那一边没有意义
计算完毕之后一定能得到最大值
**/
private int solve4(int[] height){
int left = 0;
int right = height.length - 1;

int result = -1;

while(left < right){
int tmp = (right - left) * min(height[left], height[right]);

result = max(result, tmp);

if(height[left] <= height[right])
left++;
else
right--;
}
return result;
}

private int min(int a, int b){
if(a < b)
return a;
else
return b;
}

private int max(int a, int b){
if(a < b)
return b;
else
return a;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: