暴力递归进阶

121 阅读3分钟

暴力递归进阶

题目

给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数:

给当前结果添加一个数位(i + 1)的成本为 cost[i] (cost 数组下标从 0 开始)。 总成本必须恰好等于 target 。 添加的数位中没有数字 0 。 由于答案可能会很大,请你以字符串形式返回。

如果按照上述要求无法得到任何整数,请你返回 "0" 。

示例 1:

输入:cost = [4,3,2,5,6,7,2,5,5], target = 9

输出:"7772" 解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。

所以 "7772" 的代价为 23+ 31 = 9 。

"977" 也是满足要求的数字,但 "7772" 是较大的数字。

数字 成本 1 -> 4

2 -> 3

3 -> 2

4 -> 5

5 -> 6

6 -> 7

7 -> 2

8 -> 5

9 -> 5

用动态规划解法思路

image.png

尝试:
列举可能性

从左到右,每一位都可以在不大于target(9)的情况下,使用n次

basecase:

当遍历到len(cost),剩余成本等于0时,返回成功。还不等于零时,返回失败

定义func ,输入cost,剩余成本,遍历到第i位,返回最大的整数

func l(cost []int, rest, i int) string {
   if i == len(cost) && rest!=0{
      return "-1"
   }
   if rest == 0 {
      return ""
   }
   take := ""
   for zhang := 0; zhang*cost[i] <= rest; zhang++ {
      k := ""
      //这里重复要多少个i 就生成多少个i
      for n := 0; n < zhang; n++ {
         k += fmt.Sprintf("%v", i+1)
      }
      res := l(cost, rest-zhang*cost[i], i+1, dp)
      if res == "-1" {
         continue
      }
      take = getmax(k+res, take) //返回227 2226 中比较大的那个
   }
   if take == "" {
      return "-1"
   }
   dp[i][rest] = take
   return take
}
忆搜

因为func(rest=9,i=1) 的结果需要func(9,2),func(6,2),func(3,2),func(0,2)

func(rest=6,i=1)的结果需要func(6,2),func(3,2),func(0,2)

递归会重复计算

输入值固定,得到的结果也固定,我们就可以把参数当做key,输出当做value添加缓存,建立二维数组dp [index] [rest]

func l(cost []int, rest, i int, dp [][]string) string {
   if dp[i][rest] != "-2" {
      return dp[i][rest]
   }
   if i == len(cost) && rest!=0{
         dp[i][rest] = "-1"
         return "-1"
   }
   if rest == 0 {
      dp[i][rest] = ""
      return ""
   }
   take := ""
   for zhang := 0; zhang*cost[i] <= rest; zhang++ {
      k := ""
      //这里重复要多少个i 就生成多少个i
      for n := 0; n < zhang; n++ {
         k += fmt.Sprintf("%v", i+1)
      }
      res := l(cost, rest-zhang*cost[i], i+1, dp)
      if res == "-1" {
         continue
      }
      take = getmax(k+res, take)
   }
   if take == "" {
      take = "-1"
   }
   dp[i][rest] = take
   return take
}
严格表

根据base case,和依赖关系可以画出严格表

if i == len(cost) && rest!=0{
  return "-1"
     }
   if rest == 0 {
      return ""
 }

得到边界值,最后一行“-1”和第一列的值“”

image.png

根据依赖关系

for zhang := 0; zhang*cost[i] <= rest; zhang++ {
  k := ""
  //这里重复要多少个i 就生成多少个i
  for n := 0; n < zhang; n++ {
     k += fmt.Sprintf("%v", i+1)
  }
  res := l(cost, rest-zhang*cost[i], i+1, dp)
  if res == "-1" {
     continue
  }
  take = getmax(k+res, take)
 }

func(index,rest) 依赖func(index+1,rest-zhang*cost[i]) 。

只依赖i+1层的数据,我们从index=8开始往上遍历可以得到所有数据

改写func

func dpl(cost []int, rest int) string {
    var dp = make([][]string, len(cost)+1)
    for i := 0; i <= len(cost); i++ {
        dp[i] = make([]string, rest+1)
    }
    for col := 1; col <= rest; col++ {
        dp[len(cost)][col] = "-1"
    }
    dp[len(cost)][0] = ""
    for row := 0; row <= len(cost); row++ {
        dp[row][0] = ""
    }
    for row := len(cost) - 1; row >= 0; row-- {
        for col := 1; col <= rest; col++ {
            if col-cost[row] >= 0 && dp[row][col-cost[row]] != "-1" {
                key := fmt.Sprintf("%v%v", row+1, dp[row][col-cost[row]])
                dp[row][col] = getmax(key, dp[row+1][col])
            } else {
                dp[row][col] = dp[row+1][col]
            }
        }
    }
    return dp[0][rest]
​
}

返回结果dp [0] [9]

这里的时间复杂度其实跟我们忆搜的一样,只是用严格表的形式,能利于我们观察,作出优化

斜率优化

image.png

因为f(1,6)的值是从33f(2,0)+3f(2,3)+f(2,6) ,中得到最大的整数

f(1,9)的值是从333f(2,0)+33f(2,3)+3f(2,6)+f(2,9) 中得到最大的整数,等效于3f(1,6)+f(2,9)取较大那个

每个空格都可以直接向左减去cost[i]的位置,和下方一位的位置,比较得到结果填入

package main
​
import (
   "fmt"
   "strconv"
   "strings"
)
​
func main() {
   fmt.Println(largestNumber([]int{4, 3, 2, 5, 6, 7, 2, 5, 5}, 9))
}
​
func largestNumber(cost []int, target int) string {
   res := dpl(minusCost(cost), target)
   if res == "-1" {
      return "0"
   }
   return res
}
​
func minusCost(cost []int) [][]int {
   m := make(map[int]int)
   for get, spend := range cost {
      m[spend] = get + 1
   }
   c := make([][]int, len(m))
   i := 0
   for spend, get := range m {
      c[i] = []int{spend, get}
      i++
   }
   return c
}
​
func dpl(cost [][]int, rest int) string {
   var dp = make([][]string, len(cost)+1)
   for i := 0; i <= len(cost); i++ {
      dp[i] = make([]string, rest+1)
   }
   for col := 1; col <= rest; col++ {
      dp[len(cost)][col] = "-1"
   }
   dp[len(cost)][0] = ""
   for row := 0; row <= len(cost); row++ {
      dp[row][0] = ""
   }
   key := ""
   for row := len(cost) - 1; row >= 0; row-- {
      for col := 1; col <= rest; col++ {
         if col-cost[row][0] >= 0 && dp[row][col-cost[row][0]] != "-1" {
             //比较左移成本的位置,与下方的作比较
            key = takeBigger(dp[row][col-cost[row][0]], strconv.Itoa(cost[row][1]))
            dp[row][col] = getmax(key, dp[row+1][col])
         } else {
            dp[row][col] = dp[row+1][col]
         }
      }
   }
   return dp[0][rest]
​
}
​
func takeBigger(old, new string) string {
   olds := strings.Split(old, "")
   for i := len(old) - 1; i >= 0; i-- {
      if olds[i] >= new {
         return strings.Join(olds[0:i+1], "") + new + strings.Join(olds[i+1:], "")
      }
   }
   return new + strings.Join(olds[:], "")
}
func getmax(a, b string) string {
   if b == "-1" {
      return a
   } else {
      if len(b) > len(a) {
         return b
      } else if len(a) > len(b) {
         return a
      } else {
         if a > b {
            return a
         } else {
            return b
         }
      }
   }
​
}

额外优化:

cost = [4,3,2,5,6,7,2,5,5],第三个2 和第七个2的成本是一样的,但是能得到更大的数字7,这里可以直接排除前面的2