硬币找零之递归算法|Go主题月

906 阅读3分钟

硬币找零之贪婪算法在计算找零最少硬币数量未必得到最优解。
如果要一定找到最优解,那么递归会是非常优雅的方法。

  1. 基本情况:如果要找零的金额正好和硬币的面值相同,那么只需找回1枚硬币即可
  2. 如果要找的金额和硬币的面值不同,则枚举用一枚小于找零金额的面值的硬币加上找零金额减去此硬币面值后所需的硬币,在所有枚举结果中选择硬币数量最少的方案。

第2点举例说明: 1枚1分的硬币加上找零金额减去1分之后所需的硬币;
1枚5分的硬币加上找零金额减去5分之后所需的硬币;
1枚10分的硬币加上找零金额减去10分之后所需的硬币;
1枚21分的硬币加上找零金额减去21分之后所需的硬币;
1枚25分的硬币加上找零金额减去25分之后所需的硬币。
我们需要从中找到硬币数最少的情况,如下所示:

需要的硬币最少数量=min( 1 + 需要的硬币最少数量(原金额 - 1),
	                  1 + 需要的硬币最少数量(原金额 - 5),
	                  1 + 需要的硬币最少数量(原金额 - 10),
	                  1 + 需要的硬币最少数量(原金额 - 21),
	                  1 + 需要的硬币最少数量(原金额 - 25)
            
)

用Golang实现的递归算法下的找零时使用最少数量的硬币的代码如下:

package main

import (
	"fmt"
)

func minCoinsRecursion(coinValues []int, change int) int {
	// 找零时使用最少数量硬币:递归算法
	minCoins := change
	if Find(coinValues, change){
		return 1
	} else {
		for _, coinValue := range coinValues {
			if coinValue <= change {
				numCoins := 1 + minCoinsRecursion(coinValues, change-coinValue)
				if numCoins < minCoins {
					minCoins = numCoins
				}
			}
		}
	}

	return minCoins
}

func Find(nums []int, num int) bool {
	for _, v := range nums {
		if v == num {
			return true
		}
	}
	return false
}

func main() {
	coinValues = []int{25, 21, 10, 5, 1}
	fmt.Println("需要的硬币数量:", minCoinsRecursion(coinValues, 63))
	// 需要的硬币数量: 3
}

但是上面的代码效率是非常低的。我们以找零63分为例,画出递归调用图。从图中可以发现,相同颜色的表示同一个面值,出现非常多的重复计算。

数字为37出现了4次,还有57、52、41、53,、48等出现了2次。所以有大量的时间和计算资源浪费在了重复计算上。

递归.png

优化策略:
把找零金额对应的最少硬币数的结果保存到一个map中,然后在计算新的最少硬币结果之前,先检查结果是否已经存在于map中。

使用了缓存优化的golang代码如下:

package main

import (
	"fmt"
)

func minCoinsRecursion(coinValues []int, change int, knownResult map[int]int) int {
	// 找零时使用最少数量硬币:递归算法
	minCoins := change
	if Find(coinValues, change) {
		knownResult[change] = 1
		return 1
	} else if v, ok := knownResult[change]; ok {
		return v
	} else {
		for _, coinValue := range coinValues {
			if coinValue <= change {
				numCoins := 1 + minCoinsRecursion(coinValues, change-coinValue, knownResult)
				if numCoins < minCoins {
					minCoins = numCoins
					knownResult[change] = minCoins
				}
			}
		}
	}

	return minCoins
}

func Find(nums []int, num int) bool {
	for _, v := range nums {
		if v == num {
			return true
		}
	}
	return false
}

func main() {
	coinValues := []int{25, 21, 10, 5, 1}
	knownResult := make(map[int]int)
	fmt.Println("需要的硬币数量:", minCoinsRecursion(coinValues, 63, knownResult))
	// 需要的硬币数量: 3
}