给你n个非负整数a1, a2, a3, ..., an,每个数代表坐标中的一个点(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
输入:[1,1]输出:2
示例3
输入:[4,3,2,1,4]
输出:16
提示
- n = 数组长度
- 2 <= n <=
- 0 <= 数组[i] <=
题目主要考察数组的遍历组合,很容易想到的方法是数组项i, j两两组合求其面积,并逐步遍历找出最大的面积值。其中要注意的是算面积的y值是取两项的较小值。代码v1:
/**
* 将数组项i,j两两组合,求其面积,并逐步遍历,最大面积值即为容器最大盛水量
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
let maxArea = 0
for (let i = 0; i < height.length - 1; i++) {
for (let j = i + 1; j < height.length; j++) {
// height[i], height[j]取交小的值,盛水量按最小挡板算
const x = j - i, y = Math.min(height[i], height[j])
const tempArea = x * y
maxArea = Math.max(maxArea, tempArea)
}
}
return maxArea
}
console.log(maxArea([1,8,6,2,5,4,8,3,7]))
v1版本时间复杂度为O(N²),考虑如何减少遍历次数,可使用将第二层遍历长度由n变为n/2,代码v2:
/**
* 使用双向指针,减少第二循环的遍历次数,时间复杂度变为O(N²/2)
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
const n = height.length
let maxArea = 0
for (let i = 0; i < n - 1; i++) {
for (let j = i + 1, k = n - 1; j <= k; j++, k--) {
const area1 = (j - i) * Math.min(height[i], height[j])
const area2 = (k - i) * Math.min(height[i], height[k])
maxArea = Math.max(maxArea, area1, area2)
}
}
return maxArea
}
console.log(maxArea([2,3,4,5,18,17,6]))
v2版本时间复杂度降为O(N²/2),基于此版本联想是否可以两层循环都采用方双指针式,这样时间复杂度可再降低一半O(N²/4), 代码v3:
/**
* 两次循环都采用双向指针,时间复杂度降低为O(N²/4)
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
const n = height.length
let maxArea = 0
for (let i = 0, j = n - 2; i <= j; i++, j--) {
for (let k = i + 1, l = n - 1; k <= l; k++, l--) {
const area1 = (k - i) * Math.min(height[i], height[k])
const area2 = (l - i) * Math.min(height[l], height[i])
maxArea = Math.max(maxArea, area1, area2)
if (j < l) {
const area3 = (l - j) * Math.min(height[l], height[j])
maxArea = Math.max(maxArea, area3)
}
}
}
return maxArea
}
console.log(maxArea([2,3,4,5,18,17,6]))
v1,v2,v3版本其实都是采用暴力解法,只是相对减少了遍历次数,有没有只遍历一次来实现同样的效果?考虑在遍历的过程中丢弃掉不必要的比较。
可以使用一层循环的双向指针,从i = 0, j = n - 1两边开始向内遍历,假设水槽两边围成的面积为S(i, j), h[i]、h[j]分别为水槽两边的高度。
例如当前i =0、j = 8, S(0, 8)状态下h[0] > h[8],此时指针有两种移法。如果
- 移动高的: 丢弃掉S(0, 1)、S(0, 2)、...、S(0, 7),但其中S(0, 6)的面积可能大于S(0, 8),所以此种移法不可行。
- 移动矮的:丢弃掉S(1, 8)、S(2, 8),...,S(7, 8),其中肯定不会出现比S(0, 8)面积更大的,因为宽度j - i变小,但高度不会超过h[8]。所以此类移动法可有效减少不必要的遍历。
具体实现代码如下,时间复杂度降低为O(N), 空间复杂度为O(1)。
/**
* 单层双指针循环法,由外向内遍历,找出面积的最大值
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
const n = height.length
let maxArea = 0, i = 0, j = n - 1
while(i < j) {
maxArea = Math.max(maxArea, (j - i) * Math.min(height[i], height[j]))
if (height[i] < height[j]) {
i++
} else {
j--
}
}
return maxArea
}
console.log(maxArea([2,3,4,5,18,17,6]))