双指针 接雨水

115 阅读1分钟

题目

给定一个数组arr,已知其中所有的值都是非负的,将这个数组看作一个容器,请返回容器最多能装多少水,比如,arr = [3,1,2,5,2,4],根据值画出約直方图就是容器形状,该容器可以装下5格水,再比如,arr = [4,5,1,3,2],该容器可以装下2格水

  • 题意为能装下的雨水为蓝色区域的5格

image.png

  • i 列能装下的水为 i 左边最高的高度,以及 i 右边最高的高度,最小的那个与 i 位置高度的差值 m,如果 m > 0,返回 m,如果 m < 0,说明 i 位置比一方高,水会流出,返回 0
  • 所以核心问题变成了求 i 位置左右两边的最大值,采用双指针(left、rigth)的做法,指针代表已经遍历区域的边界,在区域中通过两个变量保存左右区域的最大值
  • left、right 从首尾开始,最值就是首尾,i从 1 开始,假设左边比右边小,那么 arr[i] 能够求解了,因为此时 i 位置能够求出两边区域最大值中最小的就在左边
  • left++,如果i位置的值比左边区域的最值大,那么更新左边区域最值,继续比较两边最值,哪边小就求解哪边,然后指针移动
  • 因为从首尾开始,每个位置左右区域能够由小到大依次求得最值,因为指针不会回退,所以时间复杂度很低
function process(arr) {
  let left = 1,
    right = arr.length - 2,
    leftMax = 0,
    rightMax = 0,
    all = 0;

  // 只有 1 列或 2 列装不了雨水
  if (arr.length <= 2) {
    return 0;
  }
  while (left <= right) {
    if (leftMax < rightMax) {
      const count = Math.max(Math.min(leftMax, rightMax) - arr[left], 0);
      if (count > max) {
        all += count;
      }
      left++;
      if (arr[left] > leftMax) {
        leftMax = arr[left];
      }
    } else {
      const count = Math.max(Math.min(leftMax, rightMax) - arr[right], 0);
      if (count > max) {
        all += count;
      }
      right--;
      if (arr[right] > rightMax) {
        rightMax = arr[right];
      }
    }
  }

  return all;
}