zoukankan      html  css  js  c++  java
  • 【力扣算法】数组(1):盛最多水的容器

    原题说明

    给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

    说明:你不能倾斜容器,且 n 的值至少为 2。


    原题链接:https://leetcode-cn.com/problems/container-with-most-water


     解法一:暴力求解

    拿到这道题的第一个想法就是暴力求解。设定两层循环,第一层用于遍历容器的左边高度、第二层用于遍历容器的右边高度。然后确定题解的不定式就是(容器面积)大小的比较。

    给定表示容器边的高度数组是int[] height,我的思考过程是:

    1. 两层循环的循环变量分别是pre(左边容器高度)以及post(右边容器高度)

    2. 两个循环变量的边界分别是

    pre<height.length-1
    post<height.length

    3. 确定不定式为容器面积大小的比较

    maxsize = Math.max(maxsize, (Math.min(height[pre], height[post]))*(post-pre));    	

    4.再每次循环结束前,循环变量迭代。特别注意,在这里右边高度是在左边高度的基础上迭代

    以上分析完成后,就可以写解法,如下:

     1 public int maxSize(int[] height) {
     2     if(height.length < 2 || height == null)
     3         return 0;
     4     
     5     int pre = 0, post = 1;
     6     int maxsize = 0;
     7     while(pre<height.length-1) {
     8         while(post<height.length) {
     9             maxsize = Math.max(maxsize, (Math.min(height[pre], height[post]))*(post-pre));            
    10             post++;
    11         }          
    12         pre++;
    13         post = pre + 1;
    14     }
    15     return maxsize;
    16 }

    第一遍调试的时候,就发生两个错误:

    1)第9行代码在进行左右两边高度比较是,用得是角标而不是高度值;

    2)没有考虑第4点、也就是第13行代码,使得损失了一些容边边长组合。

     

    解法二:试图优化的解法(失败了)

    很容易想到本题应该是双指针是相关的。又本着两个指针都是从数组左端向右端遍历的惯性思维,当时的想法就是考虑合适的情况,在右指针一次遍历的时候,左指针根据面积大小比较的结果也跟着变化。

    当时我考虑了三种情况:

    1)新遍历的容器面积

    int snew = (Math.min(height[pre], height[post]))*(post-pre);
    

    2)考虑左指针相邻边的容器面积

    int k = (height[pre+1]>height[pre])? (pre+1): pre;//for the next
    int snext = (Math.min(height[k], height[post]))*(post-k);   
    

    3)考虑左右指针范围内最高边(作为左指针)的容器面积

    j = (height[post]>height[j])? post: j;//for the highest
    int shigh = (Math.min(height[j], height[post]))*(post-j);  

     然后通过比较上述三种情况与上一步的容器面积大小,进行左指针的迭代。

    if(size == snext && k != pre) {pre++;}
    if(size == shigh && j != pre) {pre=j;}

    考虑第(2)种情况,当时是觉得可以正常代替暴力求解的外循环,但是这里当遇到的问题就是,要是相邻的元素高度是相等的,采用取大值来试图迭代是要失败的;

    于是考虑到第(3)种情况,即从右指针已经遍历的范围内挑出最大值作为边长,这种情况在我当时看来,应该是对一种补充。(现在想来,要是有情况(3),就不需要情况(2)了)

    因为对于这题的理解,我的想法是,右指针的遍历已经能够确保,容器右边涉及的情况都被包含了。而左边只需要找到当前左右指针遍历的范围内的最高边长度,就可以保证容器面积最大。

    解法如下:

     1 public int maxSize(int[] height) {
     2     if(height.length<2 || height == null)
     3         return 0;
     4     
     5     int pre = 0;
     6     int post = height.length-1;
     7     int maxsize = 0;
     8     while(pre<post) {
     9         int newsize = Math.min(height[pre], height[post])*(post-pre);
    10         maxsize = Math.max(maxsize, newsize);
    11         if(height[pre]>height[post])
    12             post--;
    13         else
    14             pre++;
    15     }
    16     return maxsize;
    17 }

    后来在测试样例[75,21,3,152,13,107,163,166,32,160,41,131,7,67,56,5,153,176,29,139,61,149,176,142,64,75,170,156,73,48,148,101,70,103,53,83,11,1...]时报错了。我猜测原因,只可能是遍历的情况不完整。

    解法三:双指针(参考答案后,自己的解法,失败了)

    官方解答的双指针是从数组两端向中间遍历的。不过我最担心的是遍历的情况是否一遍就可以访问所有的情况?

    我自己的想法是,还是要从每次指针移动的情况来判断。以左指针为例,如果左指针+1的数组元素值(高度)小于等于左指针的数组元素值,考虑到整个容器的宽度还因此减小,必然不可能比原来的面积更大。

    因此,但凡指针移动,面积一定会变大,直到两端指针向中间遍历所有数组元素。但是在这种情况下,发现指针根本不动......后来想了想,还是因为数组元素可能重复,而使得指针不移动。再次印证大小比较来移动在此处行不通

    代码如下:

     1 public int solution4(int[] height) {
     2     if(height.length<2 || height == null)
     3         return 0;
     4     
     5     int pre = 0;
     6     int post = height.length-1;
     7     int maxsize = Math.min(height[pre], height[post])*(post-pre);
     8     while(pre<post) {
     9         int leftheight = Math.max(height[pre], height[pre+1]);
    10         pre = (height[pre]<height[pre+1])?(pre+1):pre;
    11         int rightheight = Math.max(height[post-1], height[post]);
    12         post = (height[post]<height[post-1])?(post-1):post;
    13         int newsize = Math.min(leftheight, rightheight)*(post-pre);
    14         
    15         maxsize = Math.max(maxsize, newsize);        
    16     }
    17     return maxsize;
    18 }

    解法四:双指针(标准答案)

    先贴上官方的解法:

     1 public int solution3(int[] height) {
     2     if(height.length<2 || height == null)
     3         return 0;
     4     
     5     int pre = 0;
     6     int post = height.length-1;
     7     int maxsize = 0;
     8     while(pre<post) {
     9         int newsize = Math.min(height[pre], height[post])*(post-pre);
    10         maxsize = Math.max(maxsize, newsize);
    11         if(height[pre]>height[post])
    12             post--;
    13         else
    14             pre++;
    15     }
    16     return maxsize;
    17 }

     解法二之所以会考虑这么细,是因为很担心出现对双指针的遍历分析不够,认为总会漏掉某些组合。解法三又是因为一开始觉得官方的解法,只是比较左右指针的元素数值,感觉一定缺乏分析?

    后来看了另一个题解,发现这个思路是完备的。

    定义左指针为i,右指针为j,每种情况是(i,j)作为容边的两边

    那么一共是两种情况,三个考虑节点

    情况(1)就是初始情况

    情况(2)是指针移动,这里有两个节点、即左指针移动和右指针移动(本质是一样的)——

    先考虑左指针,

    如果$h(i)<h(j)$,那么设左右指针范围内的任一节点k,其组合$X = { (i,k)|i < k le j} $都是可以排除的。

    给定所有的情况为

     $forall(i, k) in X, H(i, k)=left{egin{array}{l}{h(i) *(k-i), h(i) leq h(k)} \ {h(k) *(k-i), h(i)>h(k)}end{array} ight.$

    对于$h(i)<h(k)$而言,如图,则有

    $h(i) *(k-i) leq h(i) *(j-i)$(蓝色面积小于红色面积)

    对于$h(i)>h(k)$而言,如图,则有

    $h(k) *(k-i) leq h(i) *(k-i)<h(j) *(j-i)$(蓝色面积小于红色面积)

    综上,在$h(i)<h(j)$,$h(j) *(j-i)$已经包含/排除了其它所有的组合情况,因此只需要考察$h(i+1) ? h(j)$的情况即可,即可以安全地迭代$i$

    尽管$i$迭代后的面积可能更小,但是通过面积大小的比较可以让算法继续遍历。

    对于右指针,情况相同。故通过左右指针从两边向中间遍历,可以包含所有的组合情况。


     总结:

    • 从数组两端向中间遍历的双指针是解法的基础
    • 通过证明遍历的情况下能够包含所有可能,是双指针安全的根据
  • 相关阅读:
    Linux套接子(c语言)模拟http请求、应答
    709. 转换成小写字母
    1108. IP 地址无效化
    C++大作业——教职工管理系统
    贪吃蛇游戏 1.0
    getch()函数的使用方法及其返回值问题
    Open source project code
    slack Interface operation
    slack init: Simple interaction with slack bot
    mac 使用 Docker 搭建 ubuntu 环境
  • 原文地址:https://www.cnblogs.com/RicardoIsLearning/p/12015085.html
Copyright © 2011-2022 走看看