https://leetcode.com/problems/regular-expression-matching/
【描述】
Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.
Note: You may not slant the container.
【中文描述】
给一个int数组,a1,a2,a3,....an,n个数。然后假设有一个坐标系,x轴就是i,y轴的长度就是给的各个数字ai。 假想有n条竖线,每条线的下端就是点(i, 0),上端是(i, ai)。现在假想往这些线组成了N多个容器。往这些线中间倒水,要求找出其中两条线,使得在这两条线内倒水时,可以装的水最多。
————————————————————————————————————————————————————————————
【初始思路】
这道题确实很有意思,简单思考一下就知道,两条线间能盛水的最高高度取决于这两条线里哪根短。短的高度 * 两条线间距,就是所盛水的体积。
那么,怎么找这两条线呢?最简单的办法永远是brutal解法,i.j两套循环,记录中间出现的最大体积,循环结束后返回即可。时间复杂度是O(N2),leetCode肯定不会接受吧。
那还能怎么找,这就是这个题好玩的地方了!
【另辟蹊径】
假设给的数组就只有2个数字,那么很显然,最大的线就是这两条线。如果是3个数字呢,你会怎么做?我们还是可以从两边先算一个盛水量出来,然后分别算两条边和中间这条线之间的盛水量,最后3个量做个比较,给出最大值。推到N个数字呢?其实就是2个数字到3个数字的思维过程。
我们每次都从两边开始,先算一个盛水量,然后慢慢把两边往里面收,过程中记录最大的体积。等两边不断向中间靠近的时候,最终会遇到一起。那这N个数字就遍历完了,最大盛水量不就出来了么?
可是有左右两个边,怎么收是个问题。这两条边肯定一个高,一个低,按照直觉,你会舍弃哪个?保留哪个?当然,低的舍弃掉,往里看看还有没有更高的!
理性的分析呢?
我们假设每次舍弃高的一边height2(低的一边为height1),高的一边往里推移一位,得到新的newHeight2,同时假设newHeight2 > height1。由于往里推移了一位,width1变为width2,显然width2<width1。那么重新计算的area1 = height1 * width2 < height * width1. 这样求出的area,还有什么必要去和之前的area比大小么?肯定比之前的小!
如果假设newHeight2 < height1, 那么area1肯定更小了。这样岂不是越比越小?那么等到两边不断缩进到里面的时候,体积只会越比越小,最大体积相当于就是最开始两边决定的体积。这肯定不合理(虽然在个别情况下,这是有可能的,但是绝大多数情况下,这不可能!)。如果绝大多数情况下不可能,这个方法肯定不对啊!
分析结束!
【Show me the Code!!!】
1 /** 2 * 这个思路简直碉堡了! 3 * @param height 4 * @return 5 */ 6 public static int maxAreaLinear(int[] height) { 7 if(height.length==0 || height ==null) return 0; 8 int maxArea = 0; 9 int left = 0; 10 int right = height.length - 1; 11 12 while (left < right) { 13 int minHeight = Math.min(height[left], height[right]); 14 maxArea = Math.max(maxArea, minHeight * (right - left)); 15 if(height[left] <= height[right]) left++; 16 else right--; 17 } 18 return maxArea; 19 }
【follow-up】
如果两条线组成的桶可以倾斜呢?又如何找?
由于倾斜后成了一个梯形,首先温习梯形面积公式:(上底+下底)* 高/2。 那么当两条线(an, am)组成了梯形后,它的面积其实就是(am+an)*(m-n) / 2
(1)思路可以延续上面题目,还是假设初始用最左右两边围成一个梯形,然后算面积,易求area1;
(2)接下来和上面就有点区别了,因为可以倾斜,所以我们直接找i, j 之间最高的线, ak。显然,现在被分成了2个梯形,分别算出其面积,和之前的做比较。如果area1最大,那最大面积就是area1,如果后面得到的两个梯形其中一个更大。更新最大area。
(3)由于存在ak,把0->n分成了2份。 1->k-1, k+1->n-1。由于在(2)里,两个梯形一个大,一个小。我们假设右边的小,左边的大。那么右边的点k+1->n-1可以全部被舍弃掉了。因为他们高度没有ak高,他们无论怎么围,面积都不可能超过目前最大面积。
(4)再看看左边,在1->k-1中间继续找最大值,把左边梯形分成了更小的两个,再分别算面积,看哪个最大。如果原来左边的大,那么最大面积就是左边这个梯形。如果两个子梯形里有一个更大,那么重复(2)(3)(4)步骤。直到最后没有再分的余地。