接雨水面试题 - JS

1,327 阅读3分钟

初级版

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 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] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

刚看到此题,觉得好难啊,但是比较有趣,就尝试着解一下。

从图中可以看出,每个位置的接水量,取决于左右两侧高度的最小值,需要向两侧依次遍历下去,才能确保找到最大的接水量。

那我们可以先正向遍历一遍,找到左侧最大高度,再反向遍历,找到右侧最大高度。然后将两侧高度比较,取小的,再减去当前位置的高度,就是当前单位蓄水量。

/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
  let left = [], right = [];
  let n = height.length;

  left[0] = height[0];
  for (let i = 1; i < n; i++) {
    left[i] = Math.max(height[i], left[i - 1]);
  }

  right[n - 1] = height[n - 1];
  for (let j = n - 2; j >= 0; j--) {
    right[j] = Math.max(height[j], right[j + 1]);
  }

  let ans = 0;
  for (let k = 0; k < n; k++) {
    ans += Math.min(left[k], right[k]) - height[k];
  }
  return ans;
};

时间复杂度:O(n)
空间复杂度:O(n)

方法二:

  • 既然左右两侧都需要遍历,那么可以尝试使用双指针。
  • 左指针向右遍历,右指针向左遍历。
  • 左右指针需要不断更新最大值,然后比较左右指针指向的位置哪个小。
  • 如果左侧小,就说明取左侧最大高度与当前高度做差。
  • 反之,取右侧最大高度与当前高度做差。
/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
  let left = 0, right = height.length - 1;
  let leftMax = height[0], rightMax = height[height.length - 1];
  let ans = 0;
  while (left < right) {
    leftMax = Math.max(leftMax, height[left]);
    rightMax = Math.max(rightMax, height[right]);
    if (height[left] < height[right]) {
      ans += leftMax - height[left];
      left++;
    } else {
      ans += rightMax - height[right];
      right--;
    }
  }

  return ans;
};

时间复杂度:O(n)
空间复杂度:O(1)


进阶版

给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。

示例 1: 三维蓄水

输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。

示例 2: image.png

输入: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
输出: 10

  • 我们假设方块的索引为 (i, j),方块的高度为 heightMap[i][j],方块接水后的高度为 water[i][j]
  • 则我们知道方块 (i, j) 的接水后的高度为:water[i][j] = max(heightMap[i][j], min(water[i-1][j], water[i+1][j], water[i][j-1], water[i][j+1]))
  • 我们知道方块 (i, j) 实际接水的容量计算公式为 water[i][j] - heightMap[i][j]
var trapRainWater = function(heightMap) {
    const m = heightMap.length;
    const n = heightMap[0].length;
    const dirs = [-1, 0, 1, 0, -1];
    let maxHeight = 0;
    
    for (let i = 0; i < m; ++i) {
        for (let j = 0; j < n; ++j) {
            maxHeight = Math.max(maxHeight, heightMap[i][j]);
        }
    }
    const water = new Array(m).fill(0).map(() => new Array(n).fill(0));
    for (let i = 0; i < m; ++i) {
        for (let j = 0; j < n; ++j){
            water[i][j] = maxHeight;      
        }
    }  
    const qu = [];
    for (let i = 0; i < m; ++i) {
        for (let j = 0; j < n; ++j) {
            if (i == 0 || i == m - 1 || j == 0 || j == n - 1) {
                if (water[i][j] > heightMap[i][j]) {
                    water[i][j] = heightMap[i][j];
                    qu.push([i, j]);
                }
            }
        }
    } 
    while (qu.length) {
        const curr = qu.shift();
        const x = curr[0];
        const y = curr[1];
        for (let i = 0; i < 4; ++i) {
            const nx = x + dirs[i], ny = y + dirs[i + 1];
            if (nx < 0 || nx >= m || ny < 0 || ny >= n) {
                continue;
            }
            if (water[x][y] < water[nx][ny] && water[nx][ny] > heightMap[nx][ny]) {
                water[nx][ny] = Math.max(water[x][y], heightMap[nx][ny]);
                qu.push([nx, ny]);
            }
        }
    }

    let res = 0;
    for (let i = 0; i < m; ++i) {
        for (let j = 0; j < n; ++j) {
            res += water[i][j] - heightMap[i][j];
        }
    }
    return res;
};

时间复杂度:O((M^2N^2)
空间复杂度:O(MN)