乘风破浪:LeetCode真题_011_Container With Most Water
乘风破浪:LeetCode真题_011_Container With Most Water
一、前言
下面我们继续进行编程练习,可以说对于实际问题的活学活用是非常重要的。比如我们这次的题目,就需要从中找到问题的性质,然后去庖丁解牛。
二、Container With Most Water
2.1 问题理解



2.2 问题分析与解决
题意我们很容易读懂,就是求木桶的最大盛水量,底乘以高就可以了,其中底也非常容易求得,就是高需要取得两者的最小值。最简单的办法就是我们通过两个for循环,暴力的从开始遍历到结尾,这样选择最大的面积,但是会消耗很大的时间。因此,我们再想想能不能优化一下,我们可以想象有一种情况我们是会直接排除的,那就是如果我们最终选择了[i,j]区域内的面积最大,那么在i的左边肯定没有比a[i]更大的数据,并且在i的右边要选择数据的时候要选择大于a[i]的数据才可以;在j的右边也肯定没有比a[j]更大的数据,并且在j的左边要选择数据的时候要选择大于a[j]的数据才可以。于是我们定义两个指针i,j,从两端向中间收缩,收缩的顺序是先收缩min{a[i],a[j]}的指针,当我们收缩的时候,按照上面的要求,只有遇到了比自己更大的值才选择尝试计算面积,这样我们就能大大地节省时间,通过左右指针遍历到i==j的时候,将整个数组遍历一遍,程序结束。
让我们先看看官方的解答:
暴力算法:
public class Solution {
public int maxArea(int[] height) {
int maxarea = 0;
for (int i = 0; i < height.length; i++)
for (int j = i + 1; j < height.length; j++)
maxarea = Math.max(maxarea, Math.min(height[i], height[j]) * (j - i));
return maxarea;
}
}

优化算法:
public class Solution {
public int maxArea(int[] height) {
int maxarea = 0, l = 0, r = height.length - 1;
while (l < r) {
maxarea = Math.max(maxarea, Math.min(height[l], height[r]) * (r - l));
if (height[l] < height[r])
l++;
else
r--;
}
return maxarea;
}
}

注意:对于上面的算法,我们是否有疑惑的地方?我想是有的。如果仔细观察,我们会发现这样的做法会忽略当两个值相等时候的做法,无论是否正确都会简单地归给l++或者r--,这是非常不负责任的,假如本来的a[i+1]大于a[j-1],但是因为算法没有考虑到这一点,笼统的选择了r--,这样得到的结果(min{a[j-1],a[i]}*(j-1-i))肯定是小于(min{a[j],a[i+1]}*(j-1-i))的,因此可能会少比较一些这样的结果,导致最终的结果出现问题。所以官方的答案也只是参考答案,不一定完全正确的。关于这一点的评论,我们在下面会有提到,其实我们这种想法也是有局限性的,官方的还是有道理的。

我们自己的解法:
public class Solution {
/**
*
* Note: You may not slant the container.
*
* 题目大意:
* 找两条竖线然后这两条线以及X轴构成的容器能容纳最多的水。
*
* 解题思路:
* 使用贪心算法,
* 1.首先假设我们找到能取最大容积的纵线为 i, j (假定i<j),
* 那么得到的最大容积 C = min( ai , aj ) * ( j- i) ;
*
* 2.下面我们看这么一条性质:
* ①: 在 j 的右端没有一条线会比它高!假设存在 k |( j<k && ak > aj) ,
* 那么 由 ak > aj,所以 min(ai, aj, ak) =min(ai, aj) ,
* 所以由i, k构成的容器的容积C' = min(ai, aj) * (k - i) > C,
* 与C是最值矛盾,所以得证j的后边不会有比它还高的线;
*
* ②:同理,在i的左边也不会有比它高的线;这说明什么呢?
* 如果我们目前得到的候选: 设为 x, y两条线(x< y),那么能够得到比
* 它更大容积的新的两条边必然在[x, y]区间内并且 ax' >= ax , ay' >= ay;
*
* 3.所以我们从两头向中间靠拢,同时更新候选值;在收缩区间的时候优先从
* x, y中较小的边开始收缩;
*/
public int maxArea(int[] height) {
// 参数校验
if (height == null || height.length < 2) {
return 0;
}
// 记录最大的结果
int result = 0;
// 左边的竖线
int left = 0;
// 右边的竖线
int right = height.length - 1;
while (left < right) {
// 设算当前的最大值
result = Math.max(result, Math.min(height[left], height[right]) * (right - left));
// 如果右边线高
if (height[left] < height[right]) {
int k = left;
// 从[left, right - 1]中,从左向右找,找第一个高度比height[left]高的位置
while (k < right && height[k] <= height[left]) {
k++;
}
// 从[left, right - 1]中,记录第一个比原来height[left]高的位置
left = k;
}
// 左边的线高
else if (height[left] > height[right]) {
int k = right;
// 从[left + 1, right]中,从右向左找,找第一个高度比height[right]高的位置
while (k > left && height[k] <= height[right]) {
k--;
}
// 从[left, right - 1]中,记录第一个比原来height[right]高的位置
right = k;
}else{
//当两个线一样高的时候,我们需要考虑相邻的两个值,哪个大选择哪个
if(left != right && height[left+1] < height[right-1]){
right-- ;
}else if(left != right && height[left+1] >= height[right-1]){
left++ ;
}
}
}
return result;
}
}

我们的算法其实是比官方更加优化的,因为我们少比较了一些本来不用比较的结果,但是因为在算法里面嵌套了一些循环,可能会误以为是O(n~2)的时间复杂度,其实仔细观察,我们从始至终只进行了一次遍历,里面的循环只是加速了遍历而已,因此时间复杂度还是O(n),从这里我们也知道不一定两个循环嵌套就是O(n~2)的复杂度。
补充说明:相信上面的向相等情况是大家最先想到并且不理解的,但是我上面提到的问题其实并不用解决,官方的答案还是有道理的,这是因为,从我上面画的图就可以看出来,最终无论选择哪一个,都没有原来的面积大,注定被舍弃。另外即使中间的两个都大于两边的,那也可以慢慢的等到这种情况出现,如果一个大,一个小,那么即使刚开始选择了小的,之后也会不断地选择,直到靠近大的。即使选择了大的,此时得到的面积也是小于原来的,除非下次小的不断增大,因此无论如何都没有隐患的。
三、总结
通过分析,我们可以发现,很多东西都是有一些细节在里面的,如果我们能够看到并且抓住这些东西就能将时间复杂度下降一个等级,并且也需要注意有些情况下是需要考虑相等的情况的,不能笼统的归向一边,除非经过仔细的考虑和斟酌。
乘风破浪:LeetCode真题_011_Container With Most Water的更多相关文章
- 乘风破浪:LeetCode真题_041_First Missing Positive
乘风破浪:LeetCode真题_041_First Missing Positive 一.前言 这次的题目之所以说是难,其实还是在于对于某些空间和时间的限制. 二.First Missing Posi ...
- 乘风破浪:LeetCode真题_040_Combination Sum II
乘风破浪:LeetCode真题_040_Combination Sum II 一.前言 这次和上次的区别是元素不能重复使用了,这也简单,每一次去掉使用过的元素即可. 二.Combination Sum ...
- 乘风破浪:LeetCode真题_039_Combination Sum
乘风破浪:LeetCode真题_039_Combination Sum 一.前言 这一道题又是集合上面的问题,可以重复使用数字,来求得几个数之和等于目标. 二.Combination Sum ...
- 乘风破浪:LeetCode真题_038_Count and Say
乘风破浪:LeetCode真题_038_Count and Say 一.前言 这一道题目,很类似于小学的问题,但是如果硬是要将输入和结果产生数值上的联系就会产生混乱了,因此我们要打破思维定势. ...
- 乘风破浪:LeetCode真题_037_Sudoku Solver
乘风破浪:LeetCode真题_037_Sudoku Solver 一.前言 这次我们对于上次的模型做一个扩展并求解. 二.Sudoku Solver 2.1 问题 2.2 分析与解决 这道题 ...
- 乘风破浪:LeetCode真题_036_Valid Sudoku
乘风破浪:LeetCode真题_036_Valid Sudoku 一.前言 有的时候对于一些基础知识的掌握,对我们是至关重要的,比如ASCII重要字符的表示,比如一些基本类型的长度. 二.Valid ...
- 乘风破浪:LeetCode真题_035_Search Insert Position
乘风破浪:LeetCode真题_035_Search Insert Position 一.前言 这次的问题比较简单,也没有限制时间复杂度,但是要注意一些细节上的问题. 二.Search Insert ...
- 乘风破浪:LeetCode真题_034_Find First and Last Position of Element in Sorted Array
乘风破浪:LeetCode真题_034_Find First and Last Position of Element in Sorted Array 一.前言 这次我们还是要改造二分搜索,但是想法却 ...
- 乘风破浪:LeetCode真题_033_Search in Rotated Sorted Array
乘风破浪:LeetCode真题_033_Search in Rotated Sorted Array 一.前言 将传统的问题进行一些稍微的变形,这个时候我们可能无所适从了,因此还是实践出真知, ...
随机推荐
- JavaScript数据结构-11.散列
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- git 学习之撤销和删除
在实际的工作和学习中我们经常的会对文件进行修改,但是或多或少的就会发现由于某些原因修改是错误的这时候就需要对所做的修改进行撤销,更或者某些时候需要对文件进行删除.本节就会告诉大家如何操作. 撤销操作 ...
- 【LeetCode题解】20_有效的括号(Valid-Parentheses)
目录 20_有效的括号(Valid-Parentheses) 描述 解法 思路 Java 实现 Python 实现 复杂度分析 20_有效的括号(Valid-Parentheses) 描述 给定一个只 ...
- 使用Jprofiler+jmeter进行JVM性能调优
一.JProfiler简介 JProfiler 是一个商业授权的Java剖析工具,由EJ技术有限公司,针对的Java EE和Java SE应用程序开发的.它把CPU.执行绪和内存的剖析组合在一个强大的 ...
- 编译android源码遇到错误及其解决方法
升级ubuntu的14.04后,android的源码又编译错误了,一下是错误说明赫解决方法: 1.make: *** [out/host/linux-x86/obj/EXECUTABLES/aidl_ ...
- 基于struts2注解@action的@Result跳转问题——跳转到另一个action
初学ssh 基于注解的方式简单灵活,但是做一个例子的时候,添加用户AddUser 完成后 想页面跳转到 ListUser 这个action, 然后action 成功后 会跳转到list.jsp 显示 ...
- SQL 事务与锁
了解事务和锁 事务:保持逻辑数据一致性与可恢复性,必不可少的利器. 锁:多用户访问同一数据库资源时,对访问的先后次序权限管理的一种机制,没有他事务或许将会一塌糊涂,不能保证数据的安全正确读写. 死锁: ...
- sql prompt 不能用
问题描述: 安装成功后,打开sql server 工具栏不显示菜单,并弹出提示错误信息: SQL Prompt has been disabled due to an error with the r ...
- 五:SpringCloud-Zuul
九:zuul路由网关 1.概述 1.1 是什么 Zuul包含了对请求的路由和过滤两个最主要的功能: 其中==路由功能==负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础. 而==过 ...
- 九、双端队列LinkedBlockDeque
一.简介 JDK通过BlockQueue阻塞队列实现了生产者-消费者模式,生产者向队列添加数据,消费者从队列里面消费数据. 但是在有些场景里面,我们是无法区分生产者消费者的,或者说既是生产者,也是消费 ...