题目描述
给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。
如果无解,请返回-1.
【要求】
时间复杂度O(n × aim),空间复杂度On。
示例1
输入
[5,2,3],20
返回值
4
示例2
输入
[5,2,3],0
返回值
0
示例3
输入
[3,5],2
返回值
-1
解题思路
- 典型的动态规划问题,如果输入值为一个以上,可以考虑使用二维数组。输入值为面值数组 arr 和找零数 aim,所以我们可以创建一个二维数组 dp[m][n],纵轴 m 表示所有面值,横轴 n 表示使用面值 m 支付时剩余零钱数。
- 动态规划的问题需在解题之前明确阶段、状态、边界。每个阶段的任务都会推进状态的更新,最后到达边界时解决问题。本题中每个阶段的任务是“若当前剩余需找零数为 m 时,考虑是否使用一张当前面值的钱”,状态记录每个阶段任务计算出的数据,即本题中的使用货币的最小张数。数组从左上角开始计算,到右下角结束,所以边界为遍历完数组。
- “若当前剩余需找零数为 m 时,考虑是否使用一张当前面值的钱”是指当使用一张当前面值的货币,那么使用的货币数 +1 且剩余需找零数要减去当前货币面值,若不考虑使用当前面值的货币,那么货币数为使用上一种类的货币的数量且剩余找零数不需要减去。选出两种方法之中货币数最少的那一个就是最优解。
- 初始化第一种面值的数量 dp[0][?] = 剩余找零 / 当前面值,如果有余数则设为 0。
- 在 (3) 中描述的两种方法,如果最少货币数为 0 则不取
Java代码实现
public class Solution { public int minMoney (int[] arr, int aim) { // 二维数组 dp[m][n], m 表示货币种类, n表示剩余找零 int[][] dp = new int[arr.length][aim + 1]; // 初始化第一种面值,若能被剩余找零整除,就在数组中填该货币使用的张数。 for (int i = 1; i < aim + 1; ++i) { if (i % arr[0] == 0) { dp[0][i] = i / arr[0]; } } for (int m = 1; m < arr.length; ++m) { for (int n = 1; n < aim + 1; ++n) { // 若当前面值大于剩余找零,则只能不使用此种货币 if (arr[m] > n) { dp[m][n] = dp[m - 1][n]; } else if (arr[m] == n) { dp[m][n] = 1; } else if (dp[m][n - arr[m]] != 0 && dp[m - 1][n] != 0) { // 若使用一张当前货币和不使用当前货币都有值,取最小那个 dp[m][n] = min(dp[m][n - arr[m]] + 1, dp[m - 1][n]); } else { // 若其中一个为 0,取不为 0 的那一个 dp[m][n] = dp[m][n - arr[m]] != 0 ? dp[m][n - arr[m]] + 1 : dp[m - 1][n]; } } } return dp[arr.length-1][aim] == 0 ? -1 : dp[arr.length-1][aim]; } private int min (int a, int b) { return a < b ? a : b; } }