动态规划——钱币的组合方法数

/*
   数组中每个值是货币的面值,每一种面值都可以使用任意张
   规定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))
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值