「青训营 X 码上掘金」接雨水?接青豆!

50 阅读3分钟

当青训营遇上码上掘金

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,能接多少格青豆。

image.png

思考

能接到多少豆子受到什么的影响?可以类比为木桶接水,水能装多少,取决于最短的一块板。
因此这个能装多少豆子要看这格左边最高的柱子和右边最高的柱子

暴力解法

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;
}