当青训营遇上码上掘金

61 阅读2分钟

当青训营遇上码上掘金。小明为了迎战秋招,顺利上岸字节,开始了刷题,他点开了一道叫做攒青豆的题目,思考了半小时,还是不会做,小明犯了难,不禁开始想象起秋招颗粒无收,工地打灰的日子,聪明的你能教教小明,帮他重拾信心吗?

题目描述

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

Pasted image 20230213210042.png

题目分析

  1. 分析题目及示例,我们可以感觉到每个柱子上能接到的青豆数量与该柱子两侧比自己高的柱子有关,但是具体和哪个柱子有关还不清楚。
  2. 现在假设要计算柱子i上能接到的青豆数量,考虑一种暴力做法,创建两个左右指针,从i出发,那么左指针应该到哪停止呢?当遍历到i左侧的最大值时,左指针就可以停止了,因为柱子i上的能接到的青豆,不会漫过左侧最高的柱子。同理,右指针也只需要遍历到右侧的最大值。(当然,为了确定最大值,需要遍历整个数组)
  3. 上述暴力算法时间复杂度为O(n2)O(n^2),因为每个柱子找左右两侧的最大值都需要遍历一遍数组,一共需要遍历n次数组。
  4. 那么有没有方法降低时间复杂度呢,从暴力算法的思考过程我们可以发现,每个柱子上能接到的青豆数量为左右两侧最高柱子的最小值,我们正序/逆序遍历一次数组,就可以预处理出每个柱子左右两侧的最大值,在遍历一次数组,计算结果即可。

代码展现

public class Main {
   public static void main(String []args) {
      // 读取输入
      Scanner sc = new Scanner(System.in);
      int n = sc.nextInt();
      int[] height = new int[n];
      for(int i = 0;i < n;i++){
        height[i] = sc.nextInt();
      }
      sc.close();
      // 统计每个柱子左边最高的柱子的高度(包括自己)
      int[] leftMax = new int[n];
      leftMax[0] = height[0];
      for(int i = 1;i < n;i++){
        leftMax[i] = Math.max(leftMax[i - 1], height[i]);
      }
      // 统计每个柱子右边最高的柱子的高度(包括自己)
      int[] rightMax = new int[n];
      rightMax[n - 1] = height[n - 1];
      for(int i = n - 2;i >= 0;i--){
        rightMax[i] = Math.max(height[i], rightMax[i + 1]);
      }
      // 每个柱子上能接到的青豆的高度为:左右两侧最高的柱子中的较小值-柱子本身的高度
      long res = 0;
      for(int i = 0;i < n;i++){
        res += Math.min(leftMax[i], rightMax[i]) - height[i];
      }
      System.out.println(res);
   }
}

时间复杂度 O(n)O(n)

空间复杂度 O(n)O(n)