容器盛水问题算法

缘起

最近在牛客网上看到一个容器盛水问题挺有意思,更有意思的是我还想了一天,然后再下班的地铁上顿悟了···
在这里插入图片描述
这个算法题让我想起了读书时一位老师跟我说过的一句话:一个木桶能装多少水不是由最长的那根木板决定,而是由最短的那根木板决定。
这道算法题的解法就根据这句话求解。

思路

小编使用双指针的思路进行解题。
举个例子,假设给定的数组为:[4,3,3,5,1,3,2,3],我们根据这个数组画出对应的图:
在这里插入图片描述

其中,粉红色(是粉红色吧?)的方块就是可以盛水的位置,总的盛水量为所有粉色方块的总和。

  1. 我们定义两个指针,分别为leftrightleft指针开始的位置为数组的第一个元素下标,right开始的位置为数组的最后一个元素的下标。
  2. 从两边开始向中间缩进。这里有个点,目前来看,我们已知的就两个指针对应下标数组的值,其他的都不知道,那么,我们可以假装看不见其他数据,此时画出的图为:在这里插入图片描述
    顿时,数组看起来就剩下这两个元素了。
  3. 开始向中间缩进(现在就两块木板,想装水也装不了呀,只能开始缩进),先确定两块木板中,最短的那一块,也就是3,即right指针向左移动。在这里插入图片描述
    此时,2已经出来了,现在为[4,2,3],能装的水为3-2=1,即右边最大的木板减去当前的木板长度。
  4. 此时的right已经到了2的下标位置,由于现在left指针对应的值还是比right指针对应的值大,所以还是right指针往左移动。此时3出来了,现在为[4,3,2,3],能装的水从图就可以看出,那有什么规律吗?是不是右边最大的木板减去当前的木板长度?右边最大的木板为3,当前木板为3,再加上之前已经装水的量,所以当前能装水的量还是为1。在这里插入图片描述

5.重复上面的步骤,right指针还是小于left指针,还是right指针向左移动。
在这里插入图片描述

现在再算一下当前盛水的总量=之前的盛水量+(右边最大的木板长度-当前的木板长度),这一算就是3,符合。
6. right指针还是小于left指针,还是right指针向左移动。在这里插入图片描述

此时的right指针已经到了5的下标位置,那之前的公式是否还成立?答案是成立的,因为现在右边最大的木板已经变成了5,当前的木板也是5,最大的木板减去当前的木板为0,对结果并没有影响。
7. 现在right指针对应的值已经是5了,而left的指针对应的值为4,遵循移动小木板的原则,接下来应该移动left指针向右移动了。在这里插入图片描述

此时数组为[4,3,3,5,1,3,2,3],按照之前的做法,用左边最大的木板减去当前的木板得到当前盛水量(哪一边移动,就使用哪一边的最长木板来进行计算,因为移动的永远都是短木板,在短木板的那一边,只需要找到这一边最大的木板,就能保证短木板一边的木板长度不会超出当前最短的木板)
8. 此时left指针的值为3,right指针的值为5,所以left指针向右移动在这里插入图片描述

依照上面的计算方法,计算出当前的盛水量
9. 依据上面的最小移动原则,此时leftright两个指针的下标已经重合了,也就说明已经遍历完了整个数组,该退出循环了。

发现规律

从上面的解题思路来看,我们使用了双指针,哪一边的指针小,就移动哪一边的指针,移动之后,我们需要确定这一边的最大的值,然后套用最大值-当前值的公式就能得出当前的盛水量,将所有的盛水量加起来就是总的盛水量

代码

package com.mywork.niuke;

/**
 * 容器盛水问题
 *
 * 给定一个整形数组arr,已知其中所有的值都是非负的,将这个数组看作一个容器,请返回容器能装多少水。
 *
 */
public class FindMaxWater {
    public static void main(String[] args) {
//        int [] arr = {4,3,3,5,1,3,2,3};
        int [] arr = {10,1,10};
        System.out.println(maxWater(arr));
    }
    public static long maxWater (int[] arr) {
        int left = 0;   //左指针
        int right = arr.length-1;     //右指针
        int leftMax = arr[0];           //初始左边最大值
        int rightMax = arr[arr.length-1];   //初始右边最大值
        long result = 0l;
        while (left < right){
            if (arr[left] < arr[right]){
                //左指针移动
                ++left;
                //确定左边最大值(从端点开始,找最大值无非就是比较当前和保存起来的最大值
                if (leftMax<arr[left]){
                    leftMax = arr[left];
                }
                result += (leftMax -  arr[left]);
            }else {
                //右指针移动
                --right;
                //确定右边最大值
                if (rightMax<arr[right]){
                    rightMax = arr[right];
                }
                result += (rightMax - arr[right]);
            }
        }
        return result;
    }
}

题外话

java有指针吗?其实java到处都是指针,但跟C语言不同的是,C语言可以显式的声明并使用指针,但是java的没有也不允许声明和使用显式指针,但是java中对一个对象的访问却是通过指针来实现的,我们称之为隐式指针。但是上面说到的left指针和right指针并不能算是指针,只是作为遍历数组的两个标志位。

算法其实就是找出规律,用代码翻译这个规律,结合其他的数据结构,使得翻译出来的代码执行的效率更高。

搬砖不易,转载请注明出处。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值