题目描述
给定数组arr,设数组长度为n,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,代表要找的钱数,求换钱的方法数有多少种。由于方法的种数比较大,所以要求输出对进行取模后的答案。
思路
动态规划:设置dp数组,dp[i][j]表示用arr[0…i]这些货币组成金额j的方法数。根据arr[i]是否使用,可以分为两种情况:
- arr[i]不使用。此时产生的方法数为dp[i-1][j],即使用arr[0…i-1]组成金额j的方法数。
- arr[i]使用,此时产生的方法数为dp[i][j-arr[i]],即使用arr[0…i]组成金额j-arr[i]的方法数。相当于已知使用arr[i]的情况,就可以在j中去掉一张arr[i],剩下的面值为j-arr[i],此时有dp[i][j] = dp[i][j-arr[i]]。这实际上是利用以求出的值堆算法时间复杂度的一种优化。
因为题目要求的是方法总数,所以最终结果就是两种情况之和。
注意的问题
有一种情况是使用当前的货币无论如何也不能组成当前的金额,这种情况应该给dp值一个特殊的标记。当计算上述两种情况之和时,只有没有特殊标记的值才能参与运算。如果两个值都是特殊标记的,则结果也应该是特殊标记的。
代码实现
import java.util.Scanner;
public class Main{
public static void main(String []args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int aim = scanner.nextInt();
int []arr = new int [n];
for(int i = 0 ; i<n ; i++){
arr[i] = scanner.nextInt();
}
//dp[i][j]:使用arr[0...i]换金额为j的方法数
long [][]dp = new long[n][aim+1];
//答案为dp[n-1][aim]
for(int j = 0 ; j<=aim ; j++){
dp[0][j] = (j%arr[0] == 0) ?1:0;
}
for(int i = 1 ; i<n ; i++){
dp[i][0] = 1;
}
//dp[i][j] =
//1.不用第i张 dp[i-1][j]
//2.用第i张 dp[i][j-arr[i]]
for(int i = 1 ; i<n ; i++){
for(int j = 1 ; j<=aim; j++){
dp[i][j] = dp[i-1][j];
if(j-arr[i]>=0){
dp[i][j] += dp[i][j-arr[i]];
}
dp[i][j] %= 1000000000 + 7;
}
}
System.out.println(dp[n-1][aim]);
}
}