​LeetCode刷题实战11: 盛最多水的容器

程序IT圈

共 1382字,需浏览 3分钟

 · 2020-08-17

算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !


今天和大家聊的问题叫做盛最多水的容器  ,我们先来看题面:

https://leetcode-cn.com/problems/container-with-most-water


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 and n is at least 2.


The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.


题意


给定n个非负整数,表示水库当中隔板的高度。隔板之间的距离为1,当下要从n个隔板当中选出两个,在其中注水,并且要使得容纳的水尽量多。请问最多能容纳多少水?可以忽略隔板的宽度,将水库看成是正规的长方体。


样例


Input: [1,8,6,2,5,4,8,3,7]
Output: 49


题解


由于水库可以看成是正规的长方体,所以水库的体积可以简化为横截面积。也就是说我们要选择两个隔板,使得隔板之间围成的矩形面积最大。


首先思考暴力求解,我们只需要枚举矩形的两边,两边有了之后,矩形的长,也就是两边之间的距离,矩形的宽就是两边的较小值,所以复杂度是 O(n²)


这样写的代码也很简单,只有几行:


for i in range(n):  for j in range(i+1, n):    ret = max(ret, (j-i) * min(a[i], a[j]))return ret


我们来思考怎么优化,显然大部分情况下 O(n²) 的算法往往都不是最优解。考虑一个很简单的问题,为什么取最左边和最右边的隔板不行呢?这样不是矩形的长最长么?


不行的原因很简单,因为矩形的长虽然是最长,但是矩形的宽不一定很宽。有可能这样的宽很短,就像上面图中展示的一样。如果这时候的结果不是最佳值,那么最佳答案的矩形长一定小于n。如果我们用i和j指代最优解的左右两边的下标,那么显然有1 <= i < j <= n。


也就是说i,j 的位置应该在1, n的内部。我们可以想象一开始的时候i指向1,j指向n,然后逐渐移动到了最优解的位置。我们应该移动i和j,但是每次应该怎么移动呢?究竟是移动i还是移动j呢?


其实稍微想一下就能想到答案,应该移动i和j两个当中隔板比较短的那个。假设i的隔板长度小于j,即使移动i,导致碰到了更长的隔板,面积也不会变大,因为j的长度并没有变,它依旧是短板。所以我们只有移动其中较短的那个,才有让矩形面积变大的可能。


如果i和j的长度一样怎么办?答案是随便移动哪个都一样,有些同学可能还有顾虑。我们不妨使用一下我们之前介绍贪心算法的时候提到的均等假设法。


我们来举个例子,假设水库隔板的情况是:


5 10 X .. X 4 5


我们一开始的时候i指向左边的5,j指向右边的5,这时候i和j相等。如果移动左边的i,到10,面积并没有变大。接下来会一直移动右边的j,直到j遇到大于10的为止。并不会出现影响正确结果的情况。如果移动右边的j呢?其实也是一样的,因为如果j没有遇到大于5的元素,无论左边指向什么地方,面积都不会增大。当j遇到5以上的数的时候,必然会移动左边的i,一样可能增大面积。


5 10 X .. X 11 5


我们再看这个例子,如果10和11围成的结果是正确答案,那么不论先移动i还是先移动j都是一样的。本质上来说,如果矩形面积要增大,必须要i和j同时指向比当前更大的元素才行,所以先移动哪个并不会影响结果。


这样一来,写代码就方便了,我们可以人为规定如果出现相等,就移动i。写出来代码如下:


i, j = 0, n-1
ret = 0
while i < j:
  ret = max(ret, (j - i) * min(a[i], a[j]))
  if a[i] <= a[j]:
    i += 1
  else:
    j -= 1
return ret


这道题既可以认为是贪心算法,也可以认为是两指针维护区间的问题。不论怎么样解释,写出来的代码是一样的,我个人觉得还是很巧妙的,很适合初学者练手,并且难度也不是很大。希望大家都能领会。


今天的文章就到这里,如果觉得有所收获,请顺手点个在看或者转发吧,你们的支持是我最大的动力。


上期推文:

LeetCode刷题实战1:在数组上遍历出花样

LeetCode刷题实战2:用链表模拟加法

LeetCode刷题实战3:最长不重复子串

LeetCode刷题实战4:两个正序数组的中位数

LeetCode刷题实战5:判断回文子串

LeetCode刷题实战6:Z字形变换

LeetCode刷题实战7:整数反转

LeetCode刷题实战8:字符串转换整数

LeetCode刷题实战9:求解回文数

LeetCode刷题实战10:字符串正则匹配


浏览 30
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报