雨水☔️ 该怎么样才好接呢?

190 阅读1分钟

接雨水问题

接雨水对我自己而言,是一个比较难的题目,那么今天我和大家一起来揭开它神秘面纱。

image.png

我们在正式上代码之前呢,想要和大家来一次头脑风暴:

首先,题目的问题是 下雨之后能接多少雨水,这里我们需要对问题做一下拆分, 我们能接多少雨水,取决于我们每次或者说在每个阶段能留住多少水, 假设water[i], 那么最复杂的方案,就是我们需要遍历在每个i对应的 water[i],那么water[i]=? 我们可以来想一想:

我在这里呢,先给出我们需要的一个公式: water[i] = Math.min(l_max, r_max) - height[i]

这么复杂的想的话,里面内层循环中我们还需要去计算 l_maxr_max

这样做法 会导致我们的 时间复杂度为O(n^2)

但是这也是一种最为基础的解法,如果你是没有头绪的话,可以尝试一下:

暴力解法 问题拆解:

// water[i] = Math.min(l_max, r_max) - height[i]
// 思考点: r_max,l_max 怎么计算呢?
var trap = function(height) {
   let n = height.length;
   // 结果
   let res = 0;
   // 可以想一想放这里结果会不一样的 let l_max = 0,r_max = 0;
   for(let i = 1; i < n-1; i++) {
       let l_max = 0, r_max = 0;
       
       // 找左边的最大值
       for (let j = i; j >= 0; j--) {
           l_max = Math.max(l_max, height[j]);
       }
       // 找右边的最大值
       for (let j = i; j < n; j++) {
           r_max = Math.max(r_max, height[j]);
       }
       
       res +=  Math.min(l_max, r_max) - height[i]
   }
   return res;
};


// 时间复杂度 O(n^2) 空间复杂度 O(1)

那我们,想想可不可以对其中的一个 时间复杂度动一动手脚, 我们换一种思路

l_maxr_max 从一个变量变成一个数组,是不是就可以去降低遍历的时间复杂度呢?

我们提前去算出每一次 water[i] 所对应的 l_max[i]r_max[i] 是不是就可以呢. 那么下面我们来尝试一下:

// 备忘录 优化
// 同样的如果是 事先先计算出 
// l_max = []
// r_max = []

var trap = function(height) {
    let n = height.length;
    let l_max = [], r_max = [];
    let res = 0;

    // 需要定义初始值
    l_max[0] = height[0]
    r_max[n-1] = height[n-1]
    
    // 开始计算左侧
    for (let i = 1; i < n; i++) {
        // 上一次 l_max[i-1] --error
       l_max[i] = Math.max(l_max[i-1], height[i]);
    }
    // 计算右侧
    for (let j = n-2; j >= 0; j--) {
        // 上一次 r_max[j+1] --error
       r_max[j] = Math.max(r_max[j+1], height[j]);
    }
    
    for (let i = 0; i < n-1; i++) {
       res += Math.min(l_max[i], r_max[i]) - height[i];
    }
    return res;
}

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

那么, 我们在想一想,还可不可以在进行一次优化呢?

空间复杂度 也动一动呢?

大致想法的是这样子的, 借助于双指针的思路, 我们想一想, 如果可以在遍历的过程中同时 去计算 l_maxr_max 的值是否是可行的

既然有了,具体的解决方案,那么就让我们来大胆的尝试一下吧.

// 双指针的思路
// 借助于 left, right 指针从两端向中间靠拢的思路
var trap = function(height) {
   let left = 0, right = height.length-1;
   let res = 0;
   let l_max = 0, r_max = 0;
   
   while(left < right) {
       // let l_max = 0, r_max = 0; error
       l_max= Math.max(l_max, height[left]);
       r_max = Math.max(r_max, height[right]);
       
       if (l_max < r_max) {
          // 左边小 更新
          res += l_max - height[left];
          left++;
       } else {
          // 右边小 更新
          res += r_max - height[right];
          right--;
       }
   }
   
   return res;
}

那么让我们趁热打铁, 开始去攻破另一道比较相似的题目吧。

image.png

其实这道题目和上题不一样的地方在于, 柱子没有宽度.

我们似乎不用 l_maxr_max 去反复的计算来折磨我们,但是呢?

我们需要计算的是在一个合理的间隔下, 两边的 left,right 对应的高度所取得的最小值 Math.min(height[left], height[right]);

然后呢, 我们找出对应的间隔 right-left,两者的乘积是不是就是我们想要的答案呢?

下面正式进入我们的令人激动的编码环节

还是熟悉的 双指针的思路

var maxArea = function(height) {
   let left = 0, right = height.length - 1;
   let res = 0;
   
   while(left < right) {
       let temp = Math.min(height[left], height[right]) * (right - left);
       
       res = Math.max(temp, res);
       
       // 怎么移动呢? 如果左边的高度 比 右边的高度低?
       if (height[left] < height[right]) {
          left++;
       } else {
          right--;
       } 
   }
   return res;
}

到这里,我们关于接雨水的题目要告一段落了, 希望大家可以多多练习,多多理解.我们可以更好的交流心得与体会, 加油。

习题链接:

42.接雨水

11.盛水最多的容器