赞
踩
给定一个长度为 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。
-
-
-
- 输入:height = [1,1]
- 输出:1
当时读题目的时候也没明白什么意思,看了示例1的图片明白了。
容纳最多的水,是由有两条垂线中较短的一根垂线 与 两条垂线的距离决定。
换成数学语言来说,即求 蓝色区域的面积。设两条垂线对应的坐标是x = i,x = j。长度为h[i], h[j]。蓝色区域面积为 min(h[i],h[j]) * ( j - i )
第一想法求出所有两条垂线所组成的面积。然后找办法优化,当时想法既然面积只由较短的垂线和两条垂线的距离决定,那么我只要固定一条垂线,找到比这条垂线高且距离最远,那么就是当前固定的这条垂线所能构成最大的蓝色区域面积(即容纳最多的水)。
举个例子:i 是那条固定的较小的垂线,蓝色区域则是它所能构成的最大蓝色区域。要保证 i 是较小的那条。比求出所有的面积可以筛掉一些结果。
但是写代码时发现了一些问题,我是从后向前找比固定的那根垂线长的垂线,比如倒数第三根垂线。他所构成最大面积是蓝色而不是红色区域。这种情况没考虑到两条垂线距离最大,后来我想到的解决办法是,每次循环在固定垂线左右两边找到比固定垂线长且距离最大的垂线。
在暴力循环求出所有两条垂线容纳的水基础上,想通过一些条件减少循环,实际上好像并没比暴力循环优化多少。
- //把 height[i] 当作较短的,找比他高的 且 距离最长 计算面积
- public static int maxArea(int[] height) {
- //存放最大值
- int max = 0;
- //当前循环的最大值 即左右两边比较
- int max1 = 0;
- int len = 0;
- // 固定垂线 右边 比固定垂线长 的 两条垂线的距离
- int len1 = 0;
- //固定垂线 左边 比固定垂线长 的 两条垂线的距离
- int len2 = 0;
- for (int i = 0; i < height.length; i++) {
- int end = height.length - 1;
- int start = 0;
- //固定垂线右边,从最后一根循环
- while (height[i] > height[end] && end > i) {
- end--;
- }
- len1 = end - i;
- //固定垂线左边,从第一根循环
- while (height[i] > height[start] && start < i) {
- start++;
- }
- len2 = i - start;
- //比较左右两边,最长距离
- len = (len1 > len2) ? len1 : len2;
- max1 = len * height[i];
- max = (max > max1) ? max : max1;
- }
- return max;
- }
写文章时发现,可以再稍微优化一下。当 i 超过length一半时,优先找左边符合条件的垂线。当找比 height[i] 长且 i - start > length - i ,那么不需要再执行右边找符合条件垂线的循环。这种思路还是有点问题,加上这个优化也快不了多少。
两条垂线作为左右指针,每次循环移动长度较小的垂线,计算面积和比较,存储最大面积。因为所容纳最多的水是由较小的垂线决定。
- //双指针 每次循环扔掉较小的
- public static int maxArea2(int[] height) {
- int start = 0;
- int end = height.length - 1;
- int max = 0;
- while (start < end) {
- if (height[start] < height[end]) {
- max = (max > height[start] * (end - start)) ? max : height[start] * (end - start);
- start++;
- } else {
- max = (max > height[end] * (end - start)) ? max : height[end] * (end - start);
- end--;
- }
- }
- return max;
- }
相比我自己的想法,他相当于每次把较小的一个垂线计算完,就让掉了,不再参与后面循环,优化更大。
评论区看到的,感觉也肯巧妙。他把 height[i] 当作固定的。我们每次循环都存储到当前最大面积,较小垂线长度 = 面积 / 边长(即两条垂线的距离),当前循环最大边长为 lengh-1-i (最后一根垂线不在用循环,他右边没有垂线了)。当 max / lengh-1-i > hight[ i ]时,表示当前循环的 height[i] 比 目前存储最大面积所需要的最小垂线还短,所以他一定不是符合条件的垂线,他不再需要进入循环面积比较。
- public static int maxArea(int[] height) {
- int len = height.length;
- int max = 0, water = 0;
- for(int i = 0 ; i < len - 1; i++){
- // 每次循环左边固定height[i]
- //max / (len-i-1) 最大面积是,需要最小边的高。此时长度是最大,height[i]比需要最大面积最小边还小
- if(height[i] > max / (len-i-1)){
- //面积比较
- for(int j = i + 1; j < len; j++){
- water = (j-i) * Math.min(height[i], height[j]);
- if(max < water)
- max = water;
- }
- }
- }
- return max;
- }
这道题感觉并不难,再不济求出所有的面积进行比较。
做这道题时感觉很像小学时做的应用题,可以用很多办法解决问题,最后都可以得出答案。例如这道题的双指针,双指针单拿出来谁都会,但是应有到具体的问题上却想不起来。
最后做题不单单是为了最后得出答案,而是扩展思路,多种方法解决问题,这样面对其他类似的题目可以找到最优的解决办法
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。