攒青豆 | 青训营

34 阅读2分钟

当青训营遇上码上掘金

问题:

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)

image.png

以下为上图例子的解析: 输入:height = [5,0,2,1,4,0,1,0,3] 输出:17 解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

问题分析:

方法1:动态规划

左边最高的柱子与右边最高的柱子来决定最多能接多少豆子。纵向划分区间。位置i能接青豆由左右区间各自的较小值决定。采用动态规划来求解,位置i的左边的最值其实就是i-1左侧的最值与i本身进行比较。右边的最值也同理。两者的最小值-本身高度,即为青豆收集的最大量。用dp[i]存数组,遍历两次数组,分别在dp[i]中存入左侧最大值,找到右侧最大值,其中的较小值与当前位置柱子高度比,若较大则存入差值。

  • max_left[i]: 代表i位置左边最高的柱子的高度。
  • max_right[i]: 代表i位置右边最高的柱子的高度。
        if (height==null||height.length==0)
            return 0;
        int ans=0;
        int size=height.length;
        int[] left_max=new int[size];
        int[] right_max=new int[size];
        left_max[0]=height[0];
        for (int i=1;i<size;i++){
            left_max[i]=Math.max(height[i],left_max[i-1]);
        }
        right_max[size-1]=height[size-1];
        for (int i=size-2;i>=0;i--){
            right_max[i]=Math.max(height[i],right_max[i+1]);
        }
        for (int i=1;i<size-1;i++){
            ans+=Math.min(left_max[i],right_max[i])-height[i];
        }
        return ans;
    }

方法2:双指针

左右指针分别指向数组首尾,向中间扫描。先保证两边的柱子的高度,然后依次去到其中各个柱子之间能装多少青豆的问题上。

    if(heightSize==0)return 0;
    int left=0,right = heightSize-1;
    int max_left = height[0],max_right = height[heightSize-1];
    int ans=0;
    while(left <= right){
        max_left = max(max_left,height[left]);
        max_right = max(max_right,height[right]);

        if(max_left < max_right){
            ans = ans + max_left - height[left];
            left++;
        }else{
            ans = ans+max_right - height[right];
            right--;
        }

    }
    return ans;
}

方法3:单调栈

根据高度来划分区间,横向划分。找到每一个柱子的左右两边第一个比它本身高的两个柱子后,记录之间的距离,以及高低,距离*长度就是青豆面积用栈来跟踪可能储水的最长的条形块。使用栈就可以在一次遍历内完成计算。如果当前的条形块小于或等于栈顶的条形块,我们将条形块的索引入栈,意思是当前的柱子被栈中的前一个条形块界定。如果我们发现一个柱子长于栈顶,我们可以确定栈顶的柱子被当前柱子和栈的前一个柱子界定,因此我们可以弹出栈顶元素并且累加答案到 ans 。

        int ans=0,current=0;
        Stack<Integer>  stack=new Stack<>();
        while (current<height.length){
            while (!stack.empty()&&height[current]>height[stack.peek()]){
                int top= stack.pop();
                if (stack.empty())
                    break;
                int distance=current-stack.peek()-1;
                int bounded_height=Math.min(height[current],height[stack.peek()])-height[top];
                ans+=distance*bounded_height;
            }
            stack.push(current++);
        }
        return ans;
    }

总结

本次题目有多个解法可以进行解答,那么我们采用了三种方法,该方法参考了其他博友。主要是对柱子进行了区间划分,在区间内分别求面积,区间的划分有两种可能,横向与纵向,那么通过划分方式的不同我们将采用了不同的方法。栈方法比较容易,双指针较于单指针开销较小。