在算法刷题的世界里,有这样一道经典题目常常出现在各大面试和竞赛中,它就是“盛最多水的容器”问题。今天,我们就来详细剖析这道题,探讨其解题思路和优化过程。
题目描述
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。同时需要注意,不能倾斜容器。
示例
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
问题分析
首先,我们需要明确容器的容量是由什么决定的。容器的容量等于两条垂线之间的距离乘以两条垂线中较短的那条的高度。用数学公式表示就是:
area = min(height[i], height[j]) * (j - i)
其中,i 和 j 是两条垂线的索引,height[i] 和 height[j] 是对应的高度,j - i 是两条垂线之间的距离。
暴力解法
最容易想到的方法就是暴力枚举所有可能的两条垂线的组合,计算它们组成的容器的容量,然后取最大值。下面是用 JavaScript 实现的代码:
function maxArea(height) {
let maxWater = 0;
const n = height.length;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
const area = Math.min(height[i], height[j]) * (j - i);
maxWater = Math.max(maxWater, area);
}
}
return maxWater;
}
复杂度分析
- 时间复杂度:O(n²),因为有两层嵌套循环。
- 空间复杂度:O(1),只使用了常数级的额外空间。
存在的问题
暴力解法虽然简单直观,但是时间复杂度太高,当数组长度较大时,会导致运行时间过长,效率低下。因此,我们需要寻找一种更高效的解法。
双指针解法
思路
双指针解法是解决这个问题的最优方案。我们使用两个指针,一个指向数组的开头,一个指向数组的末尾。然后,我们计算这两个指针所对应的垂线组成的容器的容量,并更新最大容量。接着,我们移动较短的那条垂线对应的指针,因为移动较短的指针有可能找到更高的垂线,从而增加容器的容量;而移动较长的指针,容器的容量只会变小,因为宽度变小了,而高度不会增加。
JavaScript 实现
function maxArea(height) {
let left = 0;
let right = height.length - 1;
let maxWater = 0;
while (left < right) {
const area = Math.min(height[left], height[right]) * (right - left);
maxWater = Math.max(maxWater, area);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}
复杂度分析
- 时间复杂度:O(n),因为只需要遍历一次数组。
- 空间复杂度:O(1),只使用了常数级的额外空间。
正确性证明
可能有人会疑惑,为什么移动较短的指针是正确的呢?我们可以通过反证法来证明。假设当前 height[left] < height[right],如果我们移动 right 指针,那么新的宽度 right - left 会变小,而新的高度 min(height[left], height[newRight]) 不会超过 height[left],所以新的容器容量只会更小。因此,移动较短的指针是合理的。
代码测试
我们可以使用以下代码来测试上面的函数:
const height = [1,8,6,2,5,4,8,3,7];
console.log(maxArea(height)); // 输出: 49
总结
通过这个问题,我们学习了两种不同的解法:暴力解法和双指针解法。暴力解法虽然简单,但是时间复杂度较高;而双指针解法通过巧妙地利用问题的特性,将时间复杂度降低到了 O(n),大大提高了效率。在实际开发和面试中,我们应该尽量选择高效的算法来解决问题。
双指针算法是一种非常实用的算法技巧,在很多数组和字符串问题中都能发挥作用。希望大家通过这篇文章,对双指针算法有了更深入的理解,并且能够在遇到类似问题时灵活运用。