接雨水问题
接雨水对我自己而言,是一个比较难的题目,那么今天我和大家一起来揭开它神秘面纱。
我们在正式上代码之前呢,想要和大家来一次头脑风暴:
首先,题目的问题是 下雨之后能接多少雨水,这里我们需要对问题做一下拆分,
我们能接多少雨水,取决于我们每次或者说在每个阶段能留住多少水, 假设water[i],
那么最复杂的方案,就是我们需要遍历在每个i对应的 water[i],那么water[i]=?
我们可以来想一想:
我在这里呢,先给出我们需要的一个公式:
water[i] = Math.min(l_max, r_max) - height[i]
这么复杂的想的话,里面内层循环中我们还需要去计算 l_max和 r_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_max和r_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_max 和 r_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;
}
那么让我们趁热打铁, 开始去攻破另一道比较相似的题目吧。
其实这道题目和上题不一样的地方在于, 柱子没有宽度.
我们似乎不用 l_max 和r_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;
}
到这里,我们关于接雨水的题目要告一段落了, 希望大家可以多多练习,多多理解.我们可以更好的交流心得与体会, 加油。
习题链接: