当青训营遇上码上掘金。
话不多说,直接开始正题。
题目:攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)。
这图 emmmm
翻开书:“攒青豆”。
合上书:“接雨水”。
翻开书:“攒青豆”。
合上书:“攒雨水”。
其实就和 接雨水 这道题一样,核心思路就是找出每个柱子左右两边的最高柱子。然后。(对照图更方便理解)
暴力解法
千里之行,始于足下(指先从暴力解法出发,再寻找优化思路)
对于每个柱子,找到左右两边最高柱子后有两种情况:
-
min(左最高, 右最高) < 自身高例如图中的青豆,都满足这种情况。这种情况下,前柱子能接住的青豆数量就为:
min(左最高, 右最高) - 自身高。 -
min(做最高,右最高) >= 自身高这种情况下,根据木桶效应,当前柱子是没法接住青豆的
由此可以得到我们的算法思路如下:
- 遍历数组,找出每个柱子的左右的最高柱子高度(显然最左和最右无需找)
- 如果满足
min(左最高, 右最高) < 自身高,就计算当前能接住的青豆数量加入总和。
代码如下:
public int countBeans(int[] height) {
int sum = 0;
// 最两端不用考虑
for (int i = 1; i < height.length - 1; i++) {
int max_left = 0;
// 找出左边最高
for (int j = i - 1; j >= 0; j--) {
if (height[j] > max_left) {
max_left = height[j];
}
}
int max_right = 0;
// 找出右边最高
for (int j = i + 1; j < height.length; j++) {
if (height[j] > max_right) {
max_right = height[j];
}
}
// 找出两端较小的
int min = Math.min(max_left, max_right);
// 满足 min(左最高, 右最高) < 自身高才计算
if (min > height[i]) {
sum = sum + (min - height[i]);
}
}
return sum;
}
- 时间复杂度:。遍历每一列需要 n,找出左边最高和右边最高的柱子加起来刚好又是一个 n。
- 空间复杂度:。
优化:动态规划
暴力解法,对于每一列,我们求它左边最高的柱子和右边最高的柱子,都是重新遍历一遍所有高度,这里我们可以优化一下。
首先用两个数组,max_left [i] 代表第 i 列左边最高的墙的高度,max_right[i] 代表第 i 列右边最高的墙的高度。
对于 max_left 我们其实可以这样求:max_left [i] = Max(max_left [i-1],height[i-1])。它前边的柱子的左边的最高高度和它前边的柱子的高度选一个较大的,就是当前列左边最高的柱子了。
对于 max_right 我们可以这样求。max_right[i] = Max(max_right[i+1],height[i+1])。它后边的柱子的右边的最高高度和它后边的柱子的高度选一个较大的,就是当前列右边最高的墙了。
这样就不用在 for 循环里每次重新遍历一次求 max_left 和 max_right 了。
代码如下:
public int countBeans(int[] height) {
int sum = 0;
int[] max_left = new int[height.length];
int[] max_right = new int[height.length];
for (int i = 1; i < height.length - 1; i++) {
max_left[i] = Math.max(max_left[i - 1], height[i - 1]);
}
for (int i = height.length - 2; i >= 0; i--) {
max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
}
for (int i = 1; i < height.length - 1; i++) {
int min = Math.min(max_left[i], max_right[i]);
if (min > height[i]) {
sum = sum + (min - height[i]);
}
}
return sum;
}
时间复杂度:。
空间复杂度:,用来保存每一列左边最高的柱子和右边最高的柱子。