给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 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
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
Related Topics
贪心
数组
双指针
在看到问题之后的第一个想法就是列举出所有可能的组合,但是这个方法要么时间复杂度很大,要么使用递归使得空间复杂度很大。但我还是尝试了一下
public static int maxArea(int[] height) {
int maxArea = 0;
for (int i = 0; i < height.length - 1; i++) {
for (int j = i + 1; j < height.length; j++) {
int area = (j - i) * Math.min(height[i], height[j]);
maxArea = Math.max(maxArea, area);
}
}
return maxArea;
}
最后的运行结果就是超过了答题的时间限制。
那么,应该怎么做呢,看到题目中的提示可以知道,这道题需要使用贪心算法,
贪心算法是一种在每一步选择中都采取当前状态下最优(即最有利)的选择,以期望导致全局最优解的算法策略。这种方法在解决某些优化问题时非常有效,但并不总是得到最终的最优解。
贪心算法的基本特点:
在是否选择使用贪心算法的时候主要考虑两个问题:
在这道题中我们首先要理解题目,由于是盛水的容器,所以容量取决于最短的边以及底部长度,这里我们设定两个指针i 和 j,分别指向最左边和最右边。
问题目标:找到两个点 (i, h[i]) 和 (j, h[j])(其中 i < j),使得它们围成的矩形区域 (j - i) * min(h[i], h[j]) 最大。
贪心策略:初始时,考虑最远的两条线,即在数组的两端。每次移动较短的那条线向内,即如果 h[i] < h[j],则 i 向右移动,反之 j 向左移动。
正确性证明:状态表示:S(i, j) 表示考虑第 i 条线和第 j 条线围成的容器。短板效应:容器的容量由较短的那条线(短板)决定。即容量 C = (j - i) * min(h[i], h[j])。
移动短板的后果:假设在 S(i, j) 中 h[i] < h[j],则移动 i(即考虑 S(i+1, j))。在移动短板后,所有被排除的状态(S(i, j-1), S(i, j-2), …, S(i, i+1))的短板要么和 S(i, j) 一样高,要么更短,且底边更短。
为何不会错过最大值:在 S(i, j-1), S(i, j-2), …, S(i, i+1) 这些状态中,即使有的状态的底边比 S(i, j) 长,但由于短板更短或不变,因此它们的容量不可能超过 S(i, j)。由于 S(i, j) 已经考虑了当前情况下可能的最大容量,排除这些状态不会丢失更大的容量。
贪心的正确性:通过每次只移动短板,我们保证了每一步都尽可能地保持最大容量。因为如果移动了长板,短板不变,则容量一定减小。所以,通过这种方式枚举,我们不会错过任何可能的更大容量状态。
简而言之,该贪心算法的正确性在于它保证了每次操作都在尝试更大的容量,而忽略那些无论如何都不可能超过当前已知最大容量的状态。这种方法有效地缩小了搜索范围,同时确保了不会错过可能的最大容量解。
class Solution {
public int maxArea(int[] height) {
int i = 0, j = height.length - 1, res = 0;
while(i < j) {
res = height[i] < height[j] ?
Math.max(res, (j - i) * height[i++]):
Math.max(res, (j - i) * height[j--]);
}
return res;
}
}
在这道题中虽然穷举所有的组合并不是好的方法,但可以了解一下如何使用递归的方式来举出所有的组合方式。
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class CombinationPicker {
public static void main(String[] args) {
// 定义一个包含五个元素的数组
int[] elements = {1, 2, 3, 4, 5};
// 获取所有可能的组合
List<int[]> combinations = getAllCombinations(elements, 2);
// 打印所有组合
for (int[] combination : combinations) {
System.out.println("[" + combination[0] + ", " + combination[1] + "]");
}
// 随机选取一个组合
Random random = new Random();
int[] randomCombination = combinations.get(random.nextInt(combinations.size()));
System.out.println("Randomly selected combination: ["
+ randomCombination[0] + ", " + randomCombination[1] + "]");
}
// 方法:生成所有可能的两元素组合
public static List<int[]> getAllCombinations(int[] elements, int k) {
List<int[]> combinations = new ArrayList<>();
combine(elements, new int[k], 0, 0, combinations);
return combinations;
}
// 辅助递归方法:用于生成组合
private static void combine(int[] elements, int[] data, int start, int index, List<int[]> combinations) {
if (index == data.length) {
combinations.add(data.clone());
return;
}
for (int i = start; i <= elements.length - data.length + index; i++) {
data[index] = elements[i];
combine(elements, data, i + 1, index + 1, combinations);
}
}
}