暴力递归进阶
题目
给你一个整数数组 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
用动态规划解法思路
尝试:
列举可能性
从左到右,每一位都可以在不大于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”和第一列的值“”
根据依赖关系
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]
这里的时间复杂度其实跟我们忆搜的一样,只是用严格表的形式,能利于我们观察,作出优化
斜率优化
因为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