大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。
题目
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,...,a``n,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明: 你不能倾斜容器。
示例 1:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
解释: 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入: height = [1,1]
输出: 1
示例 3:
输入: height = [4,3,2,1,4]
输出: 16
示例 4:
输入: height = [1,2,1]
输出: 2
提示:
n == height.length2 <= n <= 1050 <= height[i] <= 104
思路
- 首先我们要搞明白什么是最大面积,就是左右两条边取最短的那条边,然后*当前左右两条边的距离;
- 那么最简单的暴力做法是,直接遍历每一种可能形成的组合,暴力枚举一边所有的值,然后把最大值记录下来返回即可。
实现
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
const n = height.length;
let maxValue = 0;
for (let i = 0; i < n; i++) {
for (j = i + 1; j < n; j++) {
// 面积等于最短的边 * 距离
let area = Math.min(height[i], height[j]) * (j - i);
maxValue = Math.max(maxValue, area);
}
}
return maxValue;
};
翻车
直接原地超时,显然这道题目没有这么简单,想想办法优化一下。
我想了一下,可以先通过两轮遍历,找到所有的有效边。
第一轮从左往右遍历,找到所有有效的左边界,如果右边的值比左边的还小说明是无效值不用考虑; 第二轮从右往左遍历一样找有效右边界。
然后枚举有效边界的所有可能性即可。
优化代码
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
const n = height.length;
let left = 0, right = n - 1;
let leftList = [ left ],
rightList = [ right ];
let prev = height[left];
// 从左到右扫描一遍
for (let i = 0; i < n; i++) {
if (height[i] > prev) {
leftList.push(i);
prev = height[i];
}
}
// 从右往左扫描一遍
prev = height[right];
for (let i = n - 1; i > 0; i--) {
if (height[i] > prev) {
rightList.push(i);
prev = height[i];
}
}
let maxValue = 0;
// 枚举所有有效值来比较
for (let i = 0; i < leftList.length; i++) {
for (let j = 0; j < rightList.length; j++) {
let ln = leftList[i], rn = rightList[j];
// 如果右边比左边索引小就不用往后走了
if (rn > ln) {
const curArea = Math.min(height[ln], height[rn]) * (rn - ln);
maxValue = Math.max(maxValue, curArea);
} else {
break;
}
}
}
return maxValue;
};
再次优化 双指针
上述代码我们虽然找到了思路,但是整体的性能并不高,我们没必要通过左右两个数组来存储所有有效的边界,只需要用贪心算法尽可能保证面积的大。我们可以用两个指针来表示边界所在的位置,每次移动最小的边界,让其往里走一格,枚举所有结果直到两者相遇。
最终代码
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
let left = 0, right = height.length - 1;
let maxValue = 0;
while (left < right) {
const minHeight = Math.min(height[left], height[right]);
maxValue = Math.max(maxValue, minHeight * (right - left));
// 最短的边往里走一个格子
if (minHeight === height[left]) {
left++;
} else {
right--;
}
}
return maxValue;
};
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。