题目描述
给定 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^2)
$$
空间复杂度
$$
O(1)
$$
双指针法
这种方法是基于对题目特性的分析,因为如果完全没有规律,只是要求最大面积,那肯定只能是暴力法。
但是题目为什么要特意强调 盛水 ?盛水这种物理行为有什么特殊的规律?就好比 有序数组 的有序性导致可以使用二分查找,盛水的具有 短板效应 ,所谓短板效应就是 容器能盛多少水完全取决于最短的那个边
基于此特性,可以归纳出以下几种情况:
如果从高的这边往低的这边走,不论遇到更高的还是更低的都没有意义
高的遇到低的,和上面同理,宽度本来就低了,高度也低,面积肯定更小,没有意义
高的那侧遇到更高的,可是由于 短板效应,面具取决短板,所以围起来的面积不受高的一侧的影响
综上,只有从短板向高的一侧遍历,并且只有 短板升高 了这种情况是存在面积变大的可能性的,因此循环就可以变成一层循环
代码实现
暴力法跳过
class Solution {public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int max = 0;
int theLower = 0;
while(left < right) {
int lowerSide;
int lower;
if(height[left] < height[right]) {
lowerSide = left;
lower = height[left];
} else {
lowerSide = right;
lower = height[right];
}
if(lower > theLower) {
// probably update max
int area = lower * (right - left);
max = Math.max(max, area);
theLower = lower; // update theLower
}
if(lowerSide == left) {left++;} else {right--;}
}
return max;
}
}
总结
说实话这种问题没有固定套路,纯靠数学和物理学的感觉,思路大概就是遇事不决先暴力,然后再思考盛水这种东西有什么特点,想到短板效应,归纳出所有情况;或者看了解题思路以后 背吧 ,遇到类似的盛水问题的时候可以想一下 短板效应
类似题目 trapping-rain-water