题目描述:
给定一个长度为 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。
题解1:
我的思路,首先双重for肯定超时,但是又没有想到好的办法,因此基于最直接的双层for下进行剪枝,遍历0号元素的时候(假设高度h,下标是x),我先计算其与len-1号元素(假设高度h1,下标是x1)形成的容器容量,之后开始剪枝;
假设现在h=<h1,现在容量是h*(x1-x) 那么x到x1之间的全部元素都不需要遍历,因为由x1移到x1-1位置时,相当于宽减小,高最大也还是h,即容量是h*(x1-1-x),也就是说x到x1之间的每一个容器容量都不会大于x和x1形成的容器容量,因此只要h<h1时,既不需要计算x到x1之间的值了。
假设现在h>h1,现在容量是h1*(x1-x) 那么当x到x1之间的某个位置(假设是x1-1)高度(假设是h2)大于h1时才有必要计算,因为如果小于h1,现在计算出来的容量相当于高度和宽度都减小了,即
h2*(x1-1-x),因此这种情况也没必要计算了。
至此完成剪枝。
说明:55/63,其实剪枝没减成功,本质还是双重for,只有一点点用。而且因为使用stream流,导致开销还增大了,所以这段代码就是史,没有什么参考价值,只是里面的stream流可以学习一下。
代码实现:
public static void main(String[] args) {
int a[] = new int[]{2,3,4,5,18,17,6};
System.out.println(maxArea(a));
}
public static int maxArea(int[] height) {
//第一遍遍历找到所有大于最后一个位置高度的数组元素,将其下标和对应的高度放入
Map<Integer,Integer> map = new HashMap<>();
int len = height.length;
for(int i =0;i<len-1;i++){
if(height[i]>height[len-1]){
map.put(i,height[i]);
}
}
//System.out.println("输出map大小: "+map.size());
double maxSize= 0;
int k = 0;
for(k=0;k<len;k++){
if(height[k]>height[len-1]){
maxSize = Math.max(maxSize,height[len-1]*(height.length-1-k));
final int kk = k;
//计算出来大于最后一块木板高度的木板们可能在当前左板(k)的左边,此时就不需要计算了,map中过滤出来的就是还在k右边的木板们
Map<Integer, Integer> tempMap = map.entrySet().stream()
.filter(entry -> entry.getKey() > kk)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
//System.out.print("第"+kk+"次遍历tempMap的大小: "+tempMap.size()+" ");
if(tempMap.size()>0){
for (Integer key : tempMap.keySet()) {
int h2 = tempMap.get(key);
int h = height[k];
if(h2>=h){
maxSize = Math.max((h*(key-k)),maxSize);
}else{
maxSize = Math.max(maxSize,(h2*(key-k)));
}
}
}
}
else{
maxSize = Math.max(maxSize,height[k]*(height.length-1-k));
}
//System.out.println("第"+k+"次遍历完的最大值: "+maxSize);
}
return (int)maxSize;
}
新知识点1:使用stream流来过滤map中满足key>kk的k-v对,并且将其过滤出来的k-v对从stream中过滤出来收集到一个新map中
Map<Integer, Integer> tempMap = map.entrySet().stream()
.filter(entry -> entry.getKey() > kk)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
需要注意的是,kk必须是一个final类型
题解2:
看了官方题解,使用双指针,需要有数学敏感度,不然想不出来。
思路是,指针L指向首元素,R指向尾元素,计算出容量,然后heigeht[L]和height[R]中较小的一个往中间移动一位,为什么?因为没移动之前的高度受较小的L和R中的较小的一个限制,移动了较小的一个之后,可能现在的高度就会增加,因此计算出来的容量也就可能会增加,尽管宽度减小了,但是高度可能会增加,乘积也就可能增大,是找出最大容量的正确办法;
但是如果现在将L和R中高度较大的一个向中间移动了,那么新容器的高度不会超过之前L和R高度的较小值h(不管移动之后得到新木板高度是多少,容器的新高度还是受制于新左右木板的较小值),并且宽度一定减小,从而容器容量一定小于等于之前容量,因此移动了LR中较大值后,后续的计算全部都无效,对于找出最大容量毫无作用了,也就是说以当前LR中较小值为边界的所有情况中,我们已经计算出来最大容量的值,其他任何固定较小值,移动较大值形成的新容器的容量都不会比当前LR形成的容器容量大了,因此,以该较小值为边界的情况都可以不考虑,因此移动较小值,就可以做到不重不漏的计算判断。
代码实现:
class Solution {
public int maxArea(int[] height) {
int maxSize=0;
int l = 0;
int r = height.length-1;
while(l<r){
maxSize = Math.max(Math.min(height[l],height[r])*(r-l),maxSize);
if(height[l]<height[r]){
l++;
}else{
r--;
}
}
return maxSize;
}
}