一个int数组,里面无重复值,可以使用其中任意张,aim=10,用任意面值任意张,问方法数是多少?
public static int way1(int []arr,int aim){
return process(arr,0,aim);
}
// 暴力递归
// 可以自由使用arr[index...]所有的值 index
// 需要搞定的钱数是rest
// 返回找零的方法数
public static int process(int []arr,int index,int rest){
if(index==arr.length){ // 没有面值可以选了
return rest == 0 ? 1: 0; // 要搞定剩下的钱数为零,则是一种方法
}
// arr[index] 0张、1张...去试,但不要超过rest的数
int ways=0;
for (int zhang = 0; arr[index]*zhang <= rest; zhang++) {
// 根据当前货币挑出某些位置累加
// rest - 已经搞的钱
ways += process(arr, index + 1, rest - arr[index]*zhang);
}
return ways;
}
// DP
// 有枚举行为 时间复杂度为O(aim*index)*O(aim) O(index*aim^2)
public static int way2(int []arr,int aim){
if(arr==null || arr.length ==0){
return 0;
}
int N =arr.length;
int [][] dp= new int [N+1][aim+1];
dp[N][0] =1;
for (int index = N-1; index >=0; index--) {
for (int rest = 0; rest <= aim;rest++) {
int ways=0;
// 枚举没必要,等于本行向左偏移一位的ways值+下面的值a。
for (int zhang = 0; arr[index]*zhang<rest; zhang++) {
// 根据当前货币挑出某些位置累加,把递归行为改成dp中取值
ways+=dp[index+1][rest-arr[index]*zhang];
}
dp[index][rest]=ways;
}
}
return dp[0][aim];
}
// 优化完的DP
// 斜率优化:填表的时候如果有枚举行为,看临近的位置能不能替换枚举行为。只和观察有关,和实际意义无关。统一技巧。
public static int way3(int []arr,int aim){
if(arr==null || arr.length ==0){
return 0;
}
int N =arr.length;
int [][] dp= new int [N+1][aim+1];
dp[N][0] =1;
// 自己下方格子的值+加左边格子的值
// 因为自己左边格子值是枚举来的,不用枚举了
for (int index = N-1; index >=0; index--) {
for (int rest = 0; rest <= aim;rest++) {
dp[index][rest]=dp[index+1][rest]; // 自己下方格子
if(rest - arr[index] >=0){ // 别越界
// rest减去自己用一张的值偏移
dp[index][rest]+= dp[index][rest-arr[index]]; // 自己本行偏移一位的ways值
}
}
}
return dp[0][aim];
}