接青豆!| 「青训营 X 码上掘金」

67 阅读3分钟

当青训营遇上码上掘金

本文以「青训营 X 码上掘金」主题四题目为主要内容,描述解题思路,点击见原题目

题目描述

本题目为经典题目“接雨水”的改编版,换汤不换药。题目中提取出的有效信息主要为:

输入:非负整数数组,代表了每个位置上柱子的单位高低(假设输入列数为n, 最高的高度为m)

输出:能够容纳的单位青豆数(整数)

暴力解

暴力解法--按行扫描

思路描述:从最底层一层一层的从左到右扫描,每到一空格都需要向数组两边搜索两边是否都有高于这个高度的墙有的话结果加一。 时间复杂度:O(n^2 x m) 空间复杂度O(1) 代码:没必要实现,复杂度过高。

暴力解法--按列扫描

思路描述:每一列能容纳多少单位的青豆,除了和自身的高度有关,还和其左边范围和右边范围内的两个最大值中相对较小的那个值相关,因此只需要按列扫描得到每一列左边范围和右边范围的最大值中的较小值,然后用该值再和每一列的高度相减,如果大于零则加到结果上,处理完成的结果即为答案。

代码如下:

//golang
//utility func
func FindMax(input []int) int {
  var n int = 0
  for _, v := range input {
    if v > n {
      n = v
    }
  }
  return n
}
//Brute Force: t: O(n^2) s:O(1)
func BruteForce(input []int) int {
  if len(input) < 1 {
    return 0
  }
  var res int = 0
  for i := 1; i < len(input) - 1; i++ {
      var lm int = FindMax(input[:i])
      var rm int = FindMax(input[i + 1:])
      if lm > rm {
        lm = rm
      }
      if lm > input[i] {
        res += lm - input[i]
      }
  }
  return res
}

时间复杂度:O(n^2) 空间复杂度:O(1)

动态规划

基本动态规划

从暴力法我们不难发现,计算每列两侧最大值的过程是有重复操作的,因此利用动态规划我们建立dp数组提前计算出每个位置上的左侧范围和右侧范围的最大值。很明显这部分的时间复杂度是O(n)。该方法的整体时间复杂度也为O(n),空间复杂度O(n)。

双指针优化动态规划

显然基本动态规划的空间复杂度是可以被优化的,因为在实际的计算中每个位置的两侧最大值都只被读取了一次。同时我们不难发现,每列能容纳的青豆单位数只对较小的一侧的最大值有精确的依赖,而对最大值较大的一侧的依赖是不精确的,即确定一列最多能容纳多少,只和本身列的高度,还有两侧最大值较小的一侧相关,因此利用双指针我们可以每次只在最大值较小的一边开始计算能够接到的青豆数,同时同步更新左侧和右侧的最大值。

代码如下:

//DP Optimization with Double Pointer: t:O(n) s:O(1)
func DynamicOp(input []int) int {
  if len(input) == 0 {
    return 0
  }
  res := 0
  lm := input[0]
  rm := input[len(input) - 1]
  lp := 1
  rp := len(input) - 2
  for lp <= rp {
    if lm < rm {//左侧开始
      if lm > input[lp] {//判断当前列是否大于左侧最大值
        res += lm - input[lp]
      } else {
        lm = input[lp]
      }
      lp += 1
    } else {//右侧开始
      if rm > input[rp] {
        res += rm - input[rp]
      } else {
        rm = input[rp]
      }
      rp -= 1
    }
  }
  return res
} 

时间复杂度O(n) 空间复杂度O(1) 详细代码片段如下: