当青训营遇上码上掘金|字节青训营|主题 4:攒青豆

62 阅读2分钟

当青训营遇上码上掘金,我选择攒青豆吃。 看到题目的第一时间我想到了11. 盛最多水的容器

暴力求解

遇到题目先写一个暴力求解,不仅可以熟悉一下题目,还能作为答案生成器来使用。

public static int powerKill(int[] height, int left, int right) {
    if (left >= right -1) {
        return 0;
    }
    int maxIndex = left;
    for (int i = left + 1; i <= right; i++) {
        if (height[i] > height[maxIndex]) {
            maxIndex = i;
        }
    }

    int secondIndex = left;
    if (maxIndex == left) {
        secondIndex =left + 1;
    }
    for (int i = left; i <= right; i++) {
        if (height[i] >= height[secondIndex] && i != maxIndex) {
            secondIndex = i;
        }
    }
    int first = 0;
    int second = 0;
    if (maxIndex > secondIndex) {
        first = secondIndex;
        second = maxIndex;
    } else {
        first = maxIndex;
        second = secondIndex ;
    }
    int t = Math.min(height[first], height[second]);
    int count = 0;
    for (int i = first + 1; i < second; i++) {
        count += t -height[i];
    }
    return count + powerKill(height, left, first) + powerKill(height, second, right);
}

两次遍历

我想本题首先可以转化成若干个类似于11. 盛最多水的容器的子问题:找到最高的两个index,时间复杂度为O(n),计算两者之间能承载的豆子,递归计算两index外侧的结果并且求和。所以如果每次找到的两个index恰好可以在待求范围的中央,则这个问题可以转化成一个时间复杂度为O(nlogn)的问题,最坏的情况也就是O(n2)。(当然,有大佬发现更类似42. 接雨水,原谅我是菜鸡)。 那么能不能在O(n)的时间复杂度解决这个问题呢?

当我尝试一次遍历就得到结果的时候,我发现在遍历的过程中,如果后面一定有一个最高的位置,高度为H,那么从左侧到最高所能攒到的豆子是可以计算的:

维护一个当前最高高度h,遇到下一个最高高度之前,每个位置i所能攒的豆子是h - height[i] 而在最高高度H之后,可以通过反向遍历,即从height.length - 1到 indexOfH 来进行计算。

代码实现:

public static int getBean(int[] height){
    if (height == null || height.length ==0) {
        return 0;
    }
    int ans = 0, maxIndex = 0;
    int prev = 0;
    // find highest index
    for (int i = 0; i < height.length; i++) {
        if (height[i] > height[maxIndex]) {
            maxIndex = i;
        }
    }
    // count highest left from left to right (highest)
    for (int i = 0; i <= maxIndex; i++) {
        if (height[i] >= prev) {
            prev = height[i];
        }else {
            ans += prev - height[i];
        }
    }
    // count from right to left (highest)
    prev = 0;
    for (int i = height.length - 1; i >= maxIndex; i--) {
        if (height[i] >= prev) {
            prev = height[i];
        }else {
            ans += prev - height[i];
        }
    }
    return ans;
}

以上是我的第一个版本。

一次遍历解决问题!

在后面看了大佬的解答后,我发现其实可以只用一次遍历就得到答案的办法。即从两侧的两个指针left, right向indexOfH靠近,每次移动对应高度较小的指针,直到两个指针相遇。

public static int finalVersion(int[] height) {
    if (height == null || height.length ==0) {
        return 0;
    }
    int left = 0, right = height.length - 1;
    int leftMax = height[left], rightMax = height[right], res = 0;
    while (left < right) {
        if (leftMax < rightMax) {
            if (height[left] <= leftMax) {
                res += leftMax - height[left++];
            } else {
                leftMax = height[left];
            }
        } else {
            if (height[right] <= rightMax) {
                res += rightMax - height[right--];
            } else {
                rightMax = height[right];
            }
        }
    }
    return res;
}

对于边界条件需要注意的是:

  1. left == right的时候循环就要停下了
  2. 在进入循环后的第二个if条件中,需要height[left] <= leftMax在条件中加入=,因为如果不加的话则会永远停在这一步,并且在高点H有多个的时候,无法继续计算。

写在最后

如果不能自己想出好的解决办法,那么能够在网上找到更好的解决办法,也是很棒的!这样鼓励自己吧~

含对数器版: