/*
数组中每个值是货币的面值,每一种面值都可以使用任意张
规定arr中都是正数且无重复值
给定钱数aim和arr,返回多少种方法组成aim
*/
//CoinsWay
func CoinsWays(arr []int, aim int) int {
if arr == nil || len(arr) == 0 || aim < 0 {
return 0
}
return processCoins(arr,0,aim)
}
// 可以自由使用 arr[index...]所有的面值,每一种面值都可以使用任意张
// 组成rest,有多少种方法
func processCoins(arr []int, index, rest int) int {
//if rest < 0 {
// return 0
//}
//
// rest >= 0
if index == len(arr) { // 没有货币可以选择,只有剩余钱是0的时候,找到一种方法
if rest == 0 {
return 1
}
return 0
}
/*
f(0,1000)
0> 0张10元 f(1,1000)
1> 1张10元 f(1,990)
2> 2张10元 f(1,980)
...
100张10元 f(1,0)
*/
ways := 0
// 当前有货币,arr[index]
for piece := 0; piece* arr[index] <= rest; piece++ { // piece * arr[index] 必不小于 rest,也就是说rest必 >= 0 所以可以省略的 开头的第一个base case
ways += processCoins(arr,index +1, rest - piece * arr[index])
}
return ways
}
func CoinsWays2(arr []int, aim int) int {
if arr == nil || len(arr) == 0 || aim < 0 {
return 0
}
dp := make([][]int,len(arr)+1)
for k := range dp {
dp[k] = make([]int,aim+1)
}
//一开始,所有的过程都没有计算呢
//dp[..][..] = -1
for i := 0; i < len(dp); i++ {
for j := 0; j < len(dp[i]); j++ {
dp[i][j] = -1
}
}
return processCoinsCache(arr,0,aim,dp)
}
//如果index 和rest的参数组合,是没算过的,dp[index][rest] == -1
//如果index 和rest的参数组合,是算过的,dp[index][rest] > -1
//自顶向下的动态规划
func processCoinsCache(arr []int, index, rest int,dp [][]int) int {
if dp[index][rest] != -1 {
return dp[index][rest]
}
if index == len(arr) { // 没有货币可以选择,只有剩余钱是0的时候,找到一种方法
if rest == 0 {
dp[index][rest] = 1
}else {
dp[index][rest] = 0
}
return dp[index][rest]
}
ways := 0
for piece := 0; piece* arr[index] <= rest; piece++ { // piece * arr[index] 必不小于 rest,也就是说rest必 >= 0 所以可以省略的 开头的第一个base case
ways += processCoinsCache(arr,index +1, rest - piece * arr[index],dp)
}
dp[index][rest] = ways
return dp[index][rest]
}
func CoinsWaysForDp(arr []int, aim int) int {
if arr == nil || len(arr) == 0 || aim < 0 {
return 0
}
dp := make([][]int,len(arr)+1)
for k := range dp {
dp[k] = make([]int,aim+1)
}
N := len(arr)
dp[N][0] = 1 //dp[N][1...aim] = 0
//普遍位置是怎么依赖的? 任何一行值,需要依赖下一行的值
for index := N - 1; index >= 0; index-- {
for rest := 0; rest <= aim; rest++ {
ways := 0
for piece := 0; piece* arr[index] <= rest; piece++ { // piece * arr[index] 必不小于 rest,也就是说rest必 >= 0 所以可以省略的 开头的第一个base case
ways += dp[index +1][rest - piece * arr[index]]
}
dp[index][rest] = ways
}
}
return dp[0][aim]
}
//优化
func CoinsWaysForDpBest(arr []int, aim int) int {
if arr == nil || len(arr) == 0 || aim < 0 {
return 0
}
dp := make([][]int,len(arr)+1)
for k := range dp {
dp[k] = make([]int,aim+1)
}
N := len(arr)
dp[N][0] = 1 //dp[N][1...aim] = 0
//普遍位置是怎么依赖的? 任何一行值,需要依赖下一行的值
for index := N - 1; index >= 0; index-- {
for rest := 0; rest <= aim; rest++ {
dp[index][rest] = dp[index+1][rest]
if rest - arr[index] >= 0 {
dp[index][rest] += dp[index][rest-arr[index]]
}
}
}
return dp[0][aim]
}
// 预处理数组,斜率优化,四边形不等式,为了省掉枚举行为
// 有一种贱人,根据最终的状态转移方程,找解释,反推过程
// 任何动态规划都是从最暴力的递归优化过来的
func TestCoinsWays(t *testing.T) {
fmt.Println(CoinsWays([]int{5,10,50,100},1000))
fmt.Println(CoinsWays2([]int{5,10,50,100},1000))
fmt.Println(CoinsWaysForDp([]int{5,10,50,100},1000))
fmt.Println(CoinsWaysForDpBest([]int{5,10,50,100},1000))
}
动态规划——钱币的组合方法数
最新推荐文章于 2022-03-31 19:43:47 发布