题目:盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表座标中的一个点 (i, ai) 。在座标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
道人第一想法就是暴力求解
这里道人说明下,暴力求解并没什么不好;道人解释下原因和自己的理解。
- 首先:计算机就是做运算的,就是擅长做重复的逻辑计算,以现在计算机的性能,一些在你眼中的高时间复杂度的工作,也许效率并不是你想象的那么低。
- 如果说第一个原因你说有些牵强,那么下面两个原因,也许能为暴力求解找出合理的解释。
- 数据量不大:在业务场景(数据量不大)的情况下,O(n)与O(n二次方)的差距并不大。
- 代码可读性强:相对而言,暴力求解算是很容易被不同的人理解的。
- 方便理清逻辑:暴力求解,其实是帮我们理清最基础的逻辑,在理清基础逻辑后,如何优化?如何转换角度?如何提高性能?暴力求解都给我们奠定了优化该题的基础。
暴力求解:任意两点(ai,aj)的容器容积的计算公式Math.min(ai,aj)*(j-i)。
代码如下
class Solution {
public int maxArea(int[] height) {
// 暴力求解
int maxArea = 0;
for (int i = 0; i < height.length; i++){
for(int j = i+1; j < height.length; j++){
if (Math.min(height[i],height[j])*(j-i) > maxArea){
maxArea = Math.min(height[i],height[j])*(j-i);
}
}
}
return maxArea;
}
}
第二思路: 双向指针(排除了很多明显小于当前容积的情况)
核心:选取最左、最右两个指针,实际上是获取了以较小高度为一边的所有容器的最大容积!!!!剔除了中间的很多明显小于当前容积的情况!!!
从头到尾遍历必须再次嵌套一层循环,那么从尾到头遍历啦?或者双向指针一起遍历?会是什么结果。
分析思路:
- 不难发现暴力求解存在着大量的重复计算。
以i=0该点为例,假若height[i]为较低的一点,那么高度则以height[i]为基准,最大容积与长度相关则height[height.length-1]为构成的最大容积(以i=0该点为一边的容积)。 - 若我们以height左右两边为起点,以两边较小值为一边,便是此时最大容积;**那么怎么移动才可能使下一个容积大于当前容积?**较大一边的指针向中间移动?由于底边减少,那么容积要想大于原来的值,高度必须大于原高度,那么由于移动的较大一边的指针,所以此时高度由原来的较小高度一边限制,因此不可能大于原容积。
- 所以,移动两边中较小高度一边的指针,才有可能在接下来的比较中获取到比原来更大的容积。
上述逻辑的演示:(记住一点只有较小高度一边的指针移动,新的容器值才可能大于上一个容器值;同时你每次获取的均是以较小边为容器一边的最大值)
代码如下:
class Solution {
public int maxArea(int[] height) {
// 双指针求解法
int maxArea = 0;
int i = 0;
int j = height.length-1;
while( j > i){
// 计算当前的最大容器值
maxArea = Math.max(Math.min(height[j],height[i])*(j-i),maxArea);
// 较小一边的指针移动
if (height[j] > height[i]){
i++;
} else {
j--;
}
}
return maxArea;
}
}