动态规划---爬楼梯和硬币组合问题

爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

输入: 3  
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

不难发现,对于要抵达的第n阶台阶,有两种方式可以抵达。

  1. 在第 (i−1) 阶后向上爬 1 阶。
  2. 在第 (i-2) 阶后向上爬 2 阶。
    得出状态转移方程为
dp[i]=dp[i−1]+dp[i−2]

因此可以得出代码如下:

func climbStairs(n int) int {
	if n <= 1 {
		return n
	}
	dp := make([]int, n+1)
	dp[1] = 1
	dp[2] = 2
	for i := 3; i <= n; i++ {
		dp[i] = dp[i-1] + dp[i-2]
	}
	return dp[n]
}

拓展:如果每次我可以爬 1 或 3 或 5 个台阶,这时候有多少种方式可以抵达呢?
原理和每次迈1或2步的时候是一样的,我们就在内部循环可以迈的台阶,进行计算有多少种抵达方式。代码如下:

func climbStairs(n int) int {
	if n <= 1 {
		return n
	}
	dp := make([]int, n+1)
	step := []int{1, 3, 5}
	dp[0] = 1
	for i := 1; i <= n; i++ {
		for j := 0; j < len(step); j++ {
			if i >= step[j] {
				dp[i] += dp[i-step[j]]
			}
		}
	}
	return dp[n]
}

硬币组合问题

给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。

 输入: n = 10
 输出:4
 解释: 有四种方式可以凑成总金额:
 10=10
 10=5+5
 10=5+1+1+1+1+1
 10=1+1+1+1+1+1+1+1+1+1

仔细思考一下,硬币组合问题和爬楼梯问题其实是极其相似的。比如对于给定n = 6的情况下,我们可以从两种情况抵达:

  1. 从 n = 5 的情况下加一个1分硬币
  2. 在 n = 1 的情况下加一个5分硬币

乍一看,代码完全应该和上面是一样的嘛!但是如果直接使用上面的代码去计算,会发现最后得出的结果比预料的要多。可以尝试下运行爬楼梯拓展中的代码,计算n=6的情况。为什么?

还是以 n = 6 的情况进行举例
在爬楼梯问题中,先上1步再上5步([1,5]) 和 先上5步再上1步([5,1]) 是2个不同的情况。
在硬币组合问题中,[1,5] 和 [5,1]属于同一个解决方案,都只是使用了一个1分硬币一个5分硬币。
即硬币组合问题中,硬币的排列顺序不会产生不同的解决方案

那么如何避免硬币的顺序对结果造成的影响呢?答案就是:调换遍历顺序,先遍历硬币,保证在考虑一种硬币的时候没有其它大面额硬币的影响,保证解决方案中始终是小硬币在前大硬币在后。避免重复计算问题,同样在n=6的情况下,解决方案计算只有[1,1,1,1,1,1]和[1,5] 两种。
代码:

func waysToChange(n int) int {
	if n <= 1 {
		return n
	}
	dp := make([]int, n+1)
	coin := []int{1, 5, 10}
	dp[0] = 1
	for i := 0; i < len(coin); i++ {
		for j := 1; j <= n; j++ {
			if j >= coin[i] {
				dp[j] += dp[j-coin[i]]
			}
		}
	}
	return dp[n]
}
posted @ 2020-04-25 16:34  傅小灰  阅读(327)  评论(0编辑  收藏  举报