【LeetCode - Java】11. 盛最多水的容器(中等)

1. 题目描述

在这里插入图片描述
在这里插入图片描述

2. 解题思路

愣头青的我遇到这种题目还是想妄想先用暴力解法试一下,虽然心中已经做好了要超时的打算,但还是忍不住先写一下唯一能下手的思路。果然不出意外传统的暴力解法是超时的,鉴于我一下子也想不出来其他解法,于是我又开始萌生了优化这个暴力算法使其变得可用的念头。

思考了一番两次for循环是雷打不动优化不掉的了,那咋整呢?只能是让某些循环跳过或停止(避免一些无意义的循环计算)。大家应该都能想得到,第一层循环是决定水箱的左边第二层循环是决定水箱的右边,那是什么情况下水箱的右边再高也没用呢(不能超越前最大值)?没错,就是在左边较矮的时候。较矮是多矮?临界值怎么界定呢?我们的目标是找出容积最大值,因此如果该较矮值与步数相乘都比当前最大值小,那么就没有必要去寻找右边了(右边比左边更高改变不了结果,更矮的话结果更差)。带着这个跳过某些循环的思路就可以容易地算出该高度的临界值是max/length_for_second_loop

有点难理解?来看看一个例子,数组是 [1,8,6,2,5,4,8,3,7]ji+1开始。

  1. 初始状态max=0height[0]=1,以坐标0为左边,容器最大的步长为length-i-1=9-0-1=8,因此以height[0]为左边的最大容积理想情况可达到1*8=8>max,因此该层循环要计算。
  2. 经过上一次循环后,max=8height[1]=8,以坐标1为左边,容器的最大步长为length-i-1=9-1-1=7,因此以height[1]为左边的最大容积理想情况可达到8*7=56>max,因此该层循环要计算。
  3. 经过上一次循环后,max=49height[2]=6,以坐标2为左边,容器的最大步长为length-i-1=9-2-1=6,因此以height[2]为左边的最大容积理想情况可达到6*6=36<max,该层循环跳过。
  4. max=49height[3]=2length-i-1=9-3-1=52*5=10<max,跳过。
  5. …跳过
  6. …跳过
  7. …跳过
  8. …跳过
  9. …跳过

图示为value[i][j]的值,表示左边为height[i],右边为height[j]的容积,有数值代表经过计算,显然该算法跳过了5个循环,能够在一定程度上提高暴力效率
在这里插入图片描述

ok,上述优化思路是把某部分的循环跳过,那么更激进一点,我们可不可以让某些无法跳过的循环早点停止呢?怎么才能早点停止?是不是意味着该循环再循环下去的值也不会比我现在的值更大了?在不考虑容器的高度的情况下,步长(底面积)越大其容积就越大,虽然这里的容器高度是很大影响的,但我们还是这样期盼着对吗?因此在设计第二层循环时候,我们可以采用逆序遍历,先从离左边最远的右边开始计算容积,然后同样的把左边当成最小值,如果该最小值乘以剩下的步数也不够当前的最大值大,那就需要提取终止该循环了,所以该早停思路的临界条件就是j-i≥max/height[i]

有点绕?再来看一个例子,数组仍然是 [1,8,6,2,5,4,8,3,7]jlength-1开始。

  1. 初始状态max=0height[0]=1j=8,以坐标0为左边,可执行的最大步长为j-i=8,因此以height[0]为左边的最大容积理想情况可达到1*8=8>max,因此该层的j坐标要计算。
  2. 经过上一个值的计算后max=8height[0]=1j=7,以坐标0为左边,可执行的最大步长为j-i=7,因此以height[0]为左边的最大容积理想情况可达到1*7=7<max,因此该层的j坐标及其后的坐标都不需要计算了,停止该层循环。
  3. 经过上一层的计算后max=8height[1]=8j=8,以坐标1为左边,可执行的最大步长为j-i=7,因此以height[1]为左边的最大容积理想情况可达到8*7=56>max,因此该层的j坐标要计算。
  4. 经过上一层的计算后max=49height[1]=8j=7,以坐标1为左边,可执行的最大步长为j-i=6,因此以height[1]为左边的最大容积理想情况可达到8*6=48<max,因此该层的j坐标及其后的坐标都不需要计算了,停止该层循环。
  5. max=49height[2]=6j=8j-i=66*6=36<max,跳过。
  6. …跳过
  7. …跳过
  8. …跳过
  9. …跳过

同样的,图示为value[i][j]的值,表示左边为height[i],右边为height[j]的容积,有数值代表经过计算,显然该算法只计算了两个值,又进一步提高了暴力的执行效率
在这里插入图片描述
没接触过此类型题目的常人能想到的思路描述完了,虽然性能远远比不上官解思路,但我感觉还是挺有意思的,但官解思路是那种“你没有碰见过你就不会做”,只能慢慢积累了。

对于官解提出的双指针解法确实很妙,理解起来很容易,但是难点其实在于其正确性证明的部分,这里不过多描述了,有兴趣可以去看看官解,这里贴上一个我认为挺有意思的哲学性描述吧!
在这里插入图片描述

3. 代码实现

3.1 顺序暴力+跳过

public int maxArea(int[] height) {
        int length = height.length;
        int max = 0;
        for (int i = 0; i < length - 1; i++) {
            if (height[i] * (length - i - 1) < max)
                continue;
            for (int j = i + 1; j < length; j++) {
                max = Math.max((j - i) * Math.min(height[i], height[j]), max);
            }
        }
        return max;
    }

3.2 逆序暴力+早停

public int maxArea(int[] height) {
        int length = height.length;
        int max = 0;
        for (int i = 0; i < length - 1; i++) {
            if (height[i] == 0)
                continue;
            for (int j = length - 1; j > i && (j - i) * height[i] >= max; j--) {
                max = Math.max((j - i) * Math.min(height[i], height[j]), max);
            }
        }
        return max;
    }

3.3 双指针

public int maxArea(int[] height) {
        int left = 0, right = height.length - 1, max = 0;
        while (left < right) {
            if (height[left] < height[right]) {
                max = Math.max(max, (right - left) * height[left]);
                left++;
            } else {
                max = Math.max(max, (right - left) * height[right]);
                right--;
            }
        }
        return max;
    }

在这里插入图片描述

3.4 对比

暴力解法的时间复杂度为O(n²),双指针解法的时间复杂度是O(n),但其实优化后暴力解法的实际复杂度已经远比O(n²)小了,不然也会时间超限;三种解法的空间复杂度都是O(1)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值