当青训营遇上码上掘金
题目
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,能接多少格青豆。
思考
能接到多少豆子受到什么的影响?可以类比为木桶接水,水能装多少,取决于最短的一块板。
因此这个能装多少豆子要看这格左边最高的柱子和右边最高的柱子。
暴力解法
public int solution(int[] height) {
int res = 0;
//最左边和最右边肯定不能接豆子,所以直接看1-height.length-2
for (int i = 1; i < height.length - 1; i++) {
int left = 0; int right = 0;
//找i左边的最长柱子
for (int j = 0; j <= i - 1; j++) {
left = Math.max(left, height[j]);
}
//找i右边的最长柱子
for (int j = i + 1; j <= height.length - 1; j++) {
right = Math.max(right, height[j]);
}
int shortt = Math.min(left,right);
if (shortt > height[i]) res += shortt - height[i];
}
return res;
}
动态规划
我们发现,对于每个i,我们都要从头开始找i左右边的最高柱子,很耗费时间。
能不能快速找到i左边的最高柱子和右边的最高柱子
如果i左边的最高柱子的下标是j,那么i+1左边的最高柱子就应该是j或i,因为j已经比i之前的所有柱子都高了。我们需要比较i和j谁高,那么它就是i + 1左边的最高柱子。
首先用两个数组,left[i]代表第 i 列左边最高的墙的高度,right[i]代表第 i 列右边最高的墙的高度。
对于left数组我们得出递推公式——left[i]= Max(left[i-1], height[i-1])。它前边的墙的左边的最高高度和它前边的墙的高度选一个较大的,就是当前列左边最高的墙了。
对于right数组也是同理,递推公式——right[i] = Max(right[i+1],height[i+1])
public int solution (int[] height) {
if (height.length == 1) return 0;
int res = 0;
int[] left = new int[height.length];
int[] right = new int[height.length];
left[0] = 0;
for (int i = 1; i < height.length; i++) {
if (height[i - 1] > left[i - 1]) {
left[i] = height[i - 1];
} else {
left[i] = left[i - 1];
}
}
right[height.length - 1] = 0;
for (int i = height.length - 2; i >= 0; i--) {
if (height[i + 1] > right[i + 1]) {
right[i] = height[i + 1];
} else {
right[i] = right[i + 1];
}
}
for (int i = 0; i < height.length; i++) {
int shortt = Math.min(left[i],right[i]);
if (shortt > height[i]) res += shortt - height[i];
}
return res;
}
双指针
动态规划的做法中,需要维护两个数组。我们还可以通过一些优化降低空间复杂度
我们维护两个指针left和right,以及两个变量leftMax,rightMax
当两指针未相遇时
- 每走到一个新的位置,我们都要更新leftMax和rightMax即
leftMax = Math.max(height[left], leftMax);和rightMax = Math.max(height[right], rightMax); - 将leftMax和rightMax看作一个桶的左右,当前桶里能装多少豆子取决于短板, 所以如果leftMax<rightMax,就说明短板在左侧即leftMax,此时能装多少豆子与rightMax无关。因此测量height[left]能装多少豆子。
- 右侧同理
public int solution (int[] height) {
if (height.length == 1) return 0;
int res = 0;
int left = 0;
int right = height.length - 1;
int leftMax = 0;
int rightMax = 0;
while (left < right) {
//如果height[left]大,那么它会成为接下去的最长左板,所以它应该是接不到水的。
leftMax = Math.max(height[left], leftMax);
//同理
rightMax = Math.max(height[right], rightMax);
//将leftMax和rightMax看作一个桶的左右,当前桶里能装多少水取决于短板
if (leftMax < rightMax) {
res += leftMax - height[left];
left++;
} else {
res += rightMax - height[right];
right--;
}
}
return res;
}