当青训营遇上码上掘金
题目
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
示例
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
解题思路
动态规划
本题和力扣中的接雨水题目解法一样,可以分为三步进行解决:
- 从左到右遍历每个柱子,找到当前柱子左边最高的柱子和右边最高的柱子
- 当前柱子能接多少青豆,就等于左右两边最高柱子中较短的那个高度减去当前柱子的高度
- 遍历完所有柱子后,所有能接的青豆的总和就是答案
其中第一步可以使用 动态规划 算法分别求解每个柱子左边最高的柱子和右边最高的柱子,动态转移方程如下
需要注意的是,leftMax[0]=0,即第一列左边最高的柱子高度为0;同样rightMax[n-1]=0,即最后一列右边最高的柱子高度为0。
代码
class Solution {
public int trap(int[] height) {
int n = height.length;
int ans = 0;
int[] leftMax = new int[n];
int[] rightMax = new int[n];
leftMax[0] = height[0];
rightMax[n-1] = height[n-1];
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i-1], height[i]);
}
for (int i = n-2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i+1], height[i]);
}
for (int i = 0; i < n; i++) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
算法时间复杂度为,空间复杂度为。
双指针法
分析动态规划部分代码可知 leftMax 和 rightMax 中的数据只会使用一次,因此可以考虑去掉这两个数组,分别使用单个变量保存。
此时解题方法为 双指针 法,双指针算法是通过两个指针,一个指向数组的左端,一个指向数组的右端,不断向中间靠近,这样就可以在 的时间内得到答案。
通过双指针法,我们可以从两端同时遍历整个数组。我们可以维护两个变量 leftMax 和 rightMax,分别表示左边最高的柱子和右边最高的柱子。
我们从两端同时遍历整个数组,每次移动指针时,我们比较左边和右边的柱子高度,如果左边的柱子高度小于右边的柱子高度,我们就移动左边的指针,否则我们移动右边的指针。
每次移动指针时,我们更新 leftMax 和 rightMax 的值,并累加答案。
在这种方法中,我们只需要维护 leftMax 和 rightMax 两个变量,而不需要额外使用数组来存储每个柱子左边最高的柱子和右边最高的柱子,这样就可以减少空间复杂度的使用。
此外,双针法在求解最大子序和,最大子矩阵,最小窗口子串,回文字符串等问题时都可以使用。
代码
class Solution {
public int trap(int[] height) {
int n = height.length;
int ans = 0;
int left = 0, right = n - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
ans += leftMax - height[left];
}
left++;
} else {
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
ans += rightMax - height[right];
}
right--;
}
}
return ans;
}
}
算法时间复杂度为,空间复杂度为。