【基础算法系列】相向双指针

经典题型

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。

说明:你不能倾斜容器。

实现思路

参考自B站零神(零茶山艾府)视频【基础算法精讲 2】

以数组 [1, 8, 6, 2, 5, 4, 8, 3, 7] 为例
首先根据木桶原理我们知道,能容纳水的高度,取决于两端高度较短的那条线,所以我们可以得出
可容纳水的体积计算公式:两端最小高度 ∗ 两端之间的距离
如果我们移动高度较大的一端,那么前者「两端最小高度」不会增加,而后者「两端之间的距离」会减小,那么这个乘积会减小。因此,我们移动高度较高那一端是不合理的。因此,我们移动高度较小的那一端。

  1. 开始左右指针分别指向数组两端1和7,此时水的体积为 v = 1*(9- 0 - 1) = 9
  2. 左边高度更小,则左指针右移,指向8,此时左右指针分别指向8,7
  3. 水的体积为Max(v, 7 * (9 - 1 - 1)) = 49
  4. 此时,右边高度更小,则右指针左移,左右指针分别指向8,3
  5. 水的体积为Max(v, 3 * (8 - 1 - 1)) = 49
  6. 同样的逻辑一直计算下去,直至左右指针相遇
  7. 最终得到,此数组能容纳水的最大体积为49

实现代码

class Solution {
    public int maxArea(int[] height) {
        int n = height.length, res = 0;
            int left = 0, right = n - 1;
            while (left < right) {
                // 容纳水的体积,等于两端高度较短的那条线的高度 * 两线之间的宽度
                res = Math.max(res, Math.min(height[left], height[right]) * (right - left));
                // 如果左边高度小于右边高度,则继续遍历左边的线
                if (height[left] <= height[right]) {
                    left++;
                } else { // 反之遍历右边的线
                    right--;
                }
            }
            return res;
    }
}

相似题型

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6

实现思路

类比于“最大可容纳水的体积”的思路

能接的雨水量还是取决于高度小的一端,而每根柱子所在位置能接多少雨水,又是取决于其高度较短一端的高度最大值。
以数组 [0,1,0,2,1,0,1,3,2,1,2,1] 为具体例子来看

  1. 开始左右指针分别指向左右两端,高度为0和1,此时记录前缀最大值preMax = 0,后缀最大值sufMax = 1
  2. 然后来计算下标为0到1的柱子和下标为n-1到n-2的柱子的接水量
  3. 由于前缀最大值小于后缀最大值,那么我们就能确定下标为0到1的柱子的接水量了,由于能接的雨水量还是取决于高度小的一端,所以后缀最大值的增大不会影响到接水量;而对于下标为n-1到n-2的柱子,前缀最大值的增大,可能会使得接水量发生变化
  4. 此时下标为0到1的柱子的接水量 = preMax - 当前柱子高度 = 0,且左指针右移
  5. 同理可以得到前缀最大值大于后缀最大值,可以计算右边柱子的接水量,且右指针左移
  6. 总结一下就是,遍历过程中分别记录左右两边的最大高度,如果preMax <= sufMax,则计算左边柱子的接水量,且左指针右移;反之,则计算右边柱子的接水量,且右指针左移,直至左右指针相遇

实现代码

class Solution {
    public int trap(int[] height) {
        int res = 0;
            int n = height.length;
            // 分别记录前缀最大值,和后缀的最大值
            int preMax = height[0], sufMax = height[n - 1];
            int left = 0, right = n - 1;
            while (left < right) {
                // 检查当前左右指针指向元素是否为前后缀的最大值
                preMax = Math.max(height[left], preMax);
                sufMax = Math.max(height[right], sufMax);
                // 当前前缀最大值小于等于后缀最大值,则可计算左边的可接雨水
                if (preMax <= sufMax) {
                    res += preMax - height[left++];
                }
				// 当前前缀最大值大于后缀最大值,则可计算右边的可接雨水
                if (sufMax < preMax) {
                    res += sufMax - height[right--];
                }
            }
            return res;
    }
}
  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值