前言
本题采用对撞指针解决,下面是对撞指针的简介
对撞指针:一般用于顺序结构中,也称左右指针。
• 对撞指针从两端向中间移动。一个指针从最左端开始,另一个从最右端开始,然后逐渐往中间逼近。
• 对撞指针的终止条件一般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
◦ left == right (两个指针指向同一个位置)
◦ left > right (两个指针错开)
一、题目链接
二、题目描述
三、算法思路
1.初步分析
题中给了张坐标图,横轴和纵轴都是从0开始的,显然是将数组下标当横轴,数组元素当纵轴
。求盛水最多,就是求哪两条边围成的容积(实际上就是个面积)最大。容积v=底*两边中较小的那一条边(短板效应),设两个指针 left , right 分别指向容器的左右两个端点,此时容器的容积 :
v = (right - left) * min( height[right], height[left])
暴力求解法
这里我们最容易想到的就是把所有边都两两配对一下,都算一下容积,最后选个最大的不就好了?不多废话,那我们就直接两个for循环开搞,管它什么时间复杂度,我先做出来跑一边看看能不能过(手动狗头)。
比较简单就不多叙述了,直接上代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int ret = 0;
// 两层 for 枚举出所有可能出现的情况
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
// 计算容积,找出最⼤的那⼀个
ret = max(ret, min(height[i], height[j]) * (j - i));
}
}
return ret;
}
};
虽然能跑,但果不其然的被制裁了。
2.更进一步
那我们只好换一种方法了,这里我选择使用对撞指针来解决问题。这是上面暴力解法的图,是不是挺像小学或初中的排列组合题啊。以下图为例,暴力法一共算了10次,我们双指针解决的就是减少算的次数。
我们可以看到将1和5算过之后就没必要再算[1,8]、[1,6]、[1,2]了,因为这些不是底变小高不变,还是底变小,高也变小都会导致容积V变小,所以算出来的容积肯定没有1,5这个组合大。所以你看暴力解法是不是[1,8]、[1,6]、[1,2]、[1,5]都得算啊,一共得算四次,而我们优化后的只需要算一次,减少了三次。既然1已经比完了,那就right++,比下一个呗。
当然这里我们默认规定的是右边的比左边的大,即height[right]>height[left],结果是算一次后直接right++就行,如果是左边的比右边的大的话,即height[left]>height[right],这不是和前一种情况思路类似嘛,算一次后后面的就全不用算了,直接left–跳过这个边界,判断下一个边界就行了。
下面是整体思路,有了上面的铺垫应该不难理解。
为了方便叙述,我们假设「左边边界」小于「右边边界」。
如果此时我们固定一个边界,改变另一个边界,水的容积会有如下变化形式:
◦ 容器的宽度一定变小。
◦ 由于左边界较小,决定了水的高度。如果改变左边界,新的水面高度不确定,但是一定不会超过右边的柱子高度,因此容器的容积可能会增大。
◦ 如果改变右边界,无论右边界移动到哪里,新的水面的高度一定不会超过左边界,也就是不会超过现在的水面高度,但是由于容器的宽度减小,因此容器的容积一定会变小的。
由此可见,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继续去判断下一个左右边界。
当我们不断重复上述过程,每次都可以舍去大量不必要的枚举过程,直到 left 与 right 相遇。期间产生的所有的容积里面的最大值,就是最终答案。
3.代码编写
C++完整代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1, ret = 0;
while (left < right) {
int v = min(height[left], height[right]) * (right - left);
ret = max(ret, v);
// 移动指针
if (height[left] < height[right])
left++;
else
right--;
}
return ret;
}
};
运行结果:
Java完整代码:
class Solution {
public int maxArea(int[] height) {
int left = 0, right = height.length - 1, ret = 0;
while (left < right) {
int v = Math.min(height[left], height[right]) * (right - left);
ret = Math.max(ret, v);
if (height[left] < height[right])
left++;
else
right--;
}
return ret;
}
}
运行结果:
复杂度
时间复杂度:
对于时间复杂度而言,因为我们就是使用左右指针在遍历原先的数组,所以呢复杂度即为 O(n)
空间复杂度
因为没开辟多余的空间,所以空间复杂度: O(1)