使用动态规划接青豆

60 阅读2分钟

当青训营遇上码上掘金

接青豆

题目如下

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

image.png

看到本题之后,第一反应是暴力求解。

暴力求解思路:

由于需要计算这样排列的柱子能接住多少青豆,我们可以按列求,通过横向遍历分别计算以每个柱子为底所在的长方形能接到多少青豆。

如何计算呢?

image.png

如图所示计算以高度为1的柱子为底的长方形能接住的青豆数目为 max(left,right)-curHeight。left为该柱子左侧的柱子的最大值,right为该柱子右侧的柱子的最大值。curHeight是该柱子的高度。

因此我们需要从左往右遍历每个柱子,并且每次遍历的时候求出该柱子左边柱子高度最大值与右边柱子高度最大值。

Java实现的代码如下

public class Main {
    public int getBean(int[] height) {
        int len=height.length-1;
        int sum=0;
        for(int i=1;i<=len-1;i++){
            int max_left=0;
            for(int j=0;j<i;j++){
                max_left=Math.max(max_left,height[j]);
            }
            int max_right=0;
            for(int j=i+1;j<=len;j++){
                max_right=Math.max(max_right,height[j]);
            }
            int min=Math.min(max_left,max_right);
            if(min>height[i]){
                sum+=(min-height[i]);
            }
        }
        return sum;
    }
}

如果采用上述的方式直接进行计算的话,复杂度为O(n^2),并且再计算柱子左右两边的高度的最值的时候右重复计算。因此可以采用动态规划的思想来进行空间换时间。

此处使用left[i]与right[i]分别表示 第i个柱子的左边的柱子与右边的柱子的最大值。

由于第0个柱子左边没有最值,第len-1个柱子右边没有最值。

left[0]=0;

right[len-1]=0;

此处有递推公式

left[i]=max(left[i-1],height[i])

right[i]=max(right[i+1],height[i+1])

也就是第i个柱子的左侧的最大值 = max ( 第 i-1 个柱子左侧的最大值 , 第 i-1 个柱子的高度)

第i个柱子的右侧的最大值 = max ( 第 i+1 个柱子右侧的最大值 , 第 i+1 个柱子的高度)

因此java代码如下

import java.util.*;

/**
* 注意:目前 Java 代码的入口类名称必须为 Main(大小写敏感)
*/
public class Main {
   public static void main(String []args) {
      System.out.println("依次输入柱子的高度,空格隔开");
      Scanner cin = new Scanner(System.in);
      String s = cin.nextLine();
      String[] ss = s.split(" ");
      int[] height = new int[ss.length];
      for(int i=0;i<height.length;i++){
        height[i]=Integer.parseInt(ss[i]);
      }
      int res = getBean(height);
      System.out.println(res);
   }
   //采用动态规划来接青豆
   public static int getBean(int[] height){
      int len = height.length;
        int res = 0;
        int[] left = new int[len];
        int[] right = new int[len];
        left[0]=0;
        right[len-1]=0;
        for(int i=1;i<len;i++){
            left[i]=Math.max(left[i-1],height[i-1]);
        }
        for(int j=len-2;j>=0;j--){
            right[j]=Math.max(right[j+1],height[j+1]);
        }
        for(int i=0;i<len;i++){
            int curMax = Math.min(left[i],right[i]);
            if(curMax>height[i]){
                res = res + (curMax-height[i]);
            }
        }
        return res;
   }
}

使用golang

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	var s string
	reader := bufio.NewReader(os.Stdin)
	s, _ = reader.ReadString('\n')
	arr := strings.Fields(s)
	fmt.Printf("arr: %v\n", arr)
	heights := make([]int, len(arr))
	for i := 0; i < len(heights); i++ {
		heights[i], _ = strconv.Atoi(arr[i])
	}
	fmt.Printf("heights: %v\n", heights)
	res := getBean(heights)
	fmt.Printf("res: %v\n", res)
}

func getBean(heights []int) int {
	var res int = 0
	length := len(heights)
	left := make([]int, length)
	right := make([]int, length)
	left[0] = 0
	right[length-1] = 0
	// 求出每个柱子左侧的柱子的最大值
	for i := 1; i < length; i++ {
		left[i] = max(left[i-1], heights[i-1])
	}
  // 求出每个柱子右侧的柱子的最大值
	for i := length - 2; i >= 0; i-- {
		right[i] = max(right[i+1], heights[i+1])
	}
	//从左到右遍历
	for i := 0; i < length; i++ {
		curMax := min(left[i], right[i])
		if curMax > heights[i] {
			res = res + (curMax - heights[i])
		}
	}
	return res
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func min(a, b int) int {
	if a > b {
		return b
	} else {
		return a
	}
}