当青训营遇上码上掘金
本文为 青训营 X 马上掘金 活动 主题4 的讲解。 活动链接:「青训营 X 码上掘金」主题创作活动入营版 开启! - 掘金 (juejin.cn)
前言
看到攒青豆这个主题,我想起了读小学的时候,班主任常教导我们在学习上不仅要取长补短,更要补齐短板,不要让短板限制我们。就好比使用木板拼接成的木桶,其盛水量会被短板所限制。这便是是著名的"短板理论"(短板理论-百度百科)。
回到题目中,一个柱子上方能够接住的青豆,与这跟柱子所在的“桶”有关。一个柱子,设其位置为 i ,则其所在的桶由 0 到 i 中最高的柱子以及 i 到 n 中最高的柱子组成,而能够接住的青豆则取决于“短板”。
ps:攒青豆与力扣 42 题 接雨水基本一样。
解法1:暴力解法
知道怎么求一个柱子能够接住的青豆后,我们就可以很简单地写出如下地解法:对于每条柱子,分别求出其左右两边最高的柱子,再求出其能接住的青豆。
代码如下:
public int soybeans(int[] height) {
// 对于每一个位置,首先求其左右最大的 height,再计算能够接到的青豆
if (height == null || height.length <= 2){
return 0;
}
int res = 0;
int len = height.length;
int lmax,rmax;
for(int i = 1; i < len-1; i ++){
lmax = -1;
rmax = -1;
for(int left = 0; left <= i; left ++){
lmax = Math.max(lmax, height[left]);
}
for(int right = i; right < len; right++){
rmax = Math.max(rmax, height[right]);
}
res += Math.min(lmax, rmax) - height[i];
}
return res;
}
时间复杂度:O(), 空间复杂度:O(1)
在求左右最高柱子的时候,为什么把自身也算进去了呢?
我们可以思考:什么情况下才能接到青豆:
- 情况1:中间柱子比左右最高的柱子都要矮,这种情况可以接到青豆;
- 情况2:中间柱子比左右最高柱子中的其中一个柱子要高,这种情况不能接到青豆
- 情况3:中间柱子比左右最高的柱子都要高,这种情况不能接到青豆
不将自身算进去的话,需要多一次判断,才能计算接到的青豆; 将自身算进去时,计算 lmax = rmax = height[i], Math.min(lmax, rmax) - height[i] 结果为 0,不影响最终结果。
解法2:动态规划
解法一对于每一条柱子,每一次都需要遍历 height 数组寻找其左右最高的柱子,这样的时间复杂度来到了 O(),我们可不可以对空间进行优化呢?
观察不难发现,对于柱子 i,其左边最高柱子的高度 ,同样其右边最高柱子的高度为 ,因此,我们可以先用动态规划的思想,求出所有柱子的左右最高柱子,再进一步求青豆的数量.
注意动态规划的 base case。
代码如下:
public int soybeans1(int[] height){
if (height == null || height.length <= 2){
return 0;
}
int res = 0;
int len = height.length;
// lrmax[i][0] 表示 i 及其左边最高的柱子,lrmax[i][1] 表示 i 及其右边最高的柱子
int[][] lrmax = new int[len][2];
lrmax[0][0] = height[0];
lrmax[len-1][1] = height[len-1];
for(int i = 1; i < len; i ++){
lrmax[i][0] = Math.max(lrmax[i-1][0], height[i]);
}
for(int i = len-2; i > 0; i --){
lrmax[i][1] = Math.max(lrmax[i+1][1], height[i]);
}
// 计算青豆
for(int i = 1; i < len-1; i ++){
res += Math.min(lrmax[i][0], lrmax[i][1]) - height[i];
}
return res;
}
时间复杂度:O(n),空间复杂度O(n)
解法3:使用双指针
这个解法参考自 Labuladong的算法小抄。
进一步优化空间复杂度:使用双指针,边走边算。 具体解析见下面代码中的注释:
public static int soybeans2(int[] height){
if (height == null || height.length <= 2){
return 0;
}
int res = 0;
int left = 0, right = height.length-1;
int lmax = 0, rmax = 0;
while(left < right){
// lmax 是柱子 0 到 left 中最高的柱子的高度
lmax = Math.max(lmax, height[left]);
// rmax 是柱子 right 到 0 中最高的柱子的高度
rmax = Math.max(rmax, height[right]);
// 如果 lamx < rmax 则表明 left 柱子的左边最高柱子一定是比右边最高柱子要矮的
// 此时就能够计算出 left 柱子能够接到的青豆数量
if (lmax < rmax){
res += lmax - height[left];
left ++;
} else {
// 否则, rmax >= lmax,表明 right 柱子的右边最高柱子的高度一定小于等于左边最高柱子的高度
// 此时能够计算出 right 柱子能够接到的青豆数量
res += rmax - height[right];
right --;
}
}
return res;
}
时间复杂度:O(n), 空间复杂度:O(1)