题目: 给定数组arr,arr中所有的值都为正数且不重复。
每个值代表一种面值的货币,每种面值可以使用任意张,
再给定一个整数aim代表要找的钱数,求换钱的种数
eg:arr = [5,10,25,1],aim = 0 return 1
arr = [5,10,25,1],aim = 15 return 6
arr = [3.5],aim = 2 return 0
思路分析:
第一种货币可以使用0-k次 aim - k * arr[0] >=0 剩下 arr.length() - 1种货币 要找 aim-k * arr[0]钱
k次累加即是答案
解法:
1-1.暴力递归
1-2.记忆搜索 时间优化
2-1.dp
2-2.dp 时间优化
2-3.dp 时间空间优化 目前最优时间复杂度O(N*aim) 空间复杂度O(aim)
import java.util.Scanner;
public class Main {
public static void main(String []args){
Scanner sc = new Scanner(System.in);
int length = sc.nextInt();
int[] arr = new int[length];
for(int i = 0;i < length;i++){
arr[i] = sc.nextInt();
}
int aim = sc.nextInt();
// System.out.println(minCost1(arr,0,aim)); //AC
//System.out.println(minCost2(arr,aim));//AC
//System.out.println(minCost3(arr,aim));//AC
//System.out.println(minCost4(arr,aim));//AC
System.out.println(minCost5(arr,aim));//AC
}
/**
* 暴力递归
* @param arr 货币的面值数组
* @param index 数组的索引
* @param aim 要找的钱数
* @return 找钱的种数
*
* 递归的写法
* 1.递归式
* 2.结束条件 (注意等号 循环里也一样)
*
* 执行情况请调试自行体会
*/
public static int minCost1(int[] arr,int index,int aim){
if(arr == null || arr.length < 1 || aim < 0){
return 0;
}
int res = 0;
if(index == arr.length){
res = aim == 0 ? 1 : res; //每种情况的结束 res是0 加的尾数要么0 要么 1
//等价 res = aim == 0 ? 1 : 0;
}else{
for(int i = 0;aim - i * arr[index] >= 0;i++){ //0 - k次 当前货币 求和
res += minCost1(arr,index + 1,aim - i * arr[index]); //当前arr[i]情况下 剩下的找钱种数 //所以说暴力
}
}
return res;
}
/**
* 记忆搜索
* @param arr 货币的面值数组
* @param aim 要找的钱数
* @return 找钱的种数
*
* 分开写方法 可以减少递归过程中不必要的条件判断
*/
public static int minCost2(int[] arr,int aim){
if(arr == null || arr.length == 0 || aim < 0){
return 0;
}
int[][] map = new int[arr.length + 1][aim + 1]; //标记数组
return MemorySearch(arr,0,aim,map);
}
/**
*记忆搜索
* @param arr 货币的面值数组
* @param index 数组的索引
* @param aim 要找的钱数
* @param map 标记数组 行 索引 列 要找的钱数
* 初始值为0 - 1代表返回值0(与初始值重复 特殊化才能起标记作用) 剩下的是其他递归返回值
* @return 找钱的种数
*
*/
public static int MemorySearch(int[] arr,int index,int aim,int[][] map){
int mapValue = 0;
int res = 0;
if(index == arr.length){
res = aim == 0 ? 1 : 0;
}else{
for(int i = 0; aim - i * arr[index] >= 0;i++){
// mapValue = map[i][aim - i * arr[index]];
mapValue = map[index + 1][aim - i * arr[index]];
if(mapValue != 0){ //如果不为初始值 直接取来用 特殊的 -1 为 0 其他情况不会出现负值
res += mapValue == -1 ? 0 : mapValue;
}else{
res += MemorySearch(arr,index + 1,aim - i * arr[index],map);
}
}
}
map[index][aim] = res == 0 ? -1 : res; //标记 当前递归过程的返回值
return res;
}
/**
* 动态规划
* @param arr 货币面值数组
* @param aim 要找的钱数
* @return 组成的种数
*
* 动态转移方程 (基本思路版)
* dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i]] + dp[i - 1][j - 2 * arr[i]]+...+dp[i - 1][j - k * arr[i]] ; k为不等式大于等于0 的最大整数
*/
public static int minCost3(int[] arr,int aim){
if(arr == null || arr.length == 0 || aim < 0){
return 0;
}
int[][] dp = new int[arr.length ][aim + 1];
for(int i = 0;i < arr.length;i++){
dp[i][0] = 1; //第一列初始化
}
for(int j = 1;j * arr[0] <= aim;j++){
dp[0][j * arr[0]] = 1; //第一行初始化 只有第一种面值的倍数有一种方法
}
// int num;
for(int i = 1;i < arr.length;i++){
for(int j = 1;j <= aim;j++){
//num = 0;
for(int k = 0;j - k * arr[i] >= 0;k++) { //因为此处用arr[i] 所以定义arr.length() 初始化从0开始
dp[i][j] += dp[i - 1][j - k * arr[i]];
// num += dp[i - 1][j - k * arr[i]];
}
//dp[i][j] = num;
}
}
return dp[arr.length - 1][aim];
}
/**
*动态规划
* @param arr 货币面值数组
* @param aim 要找的钱数
* @return 组成的种数
*
* 动态转移方程 (优化)
* 对比上一种方程组 令j - arr[i] = k
* 得到 dp[i][j] = dp[i - 1][j] + dp[i - 1][k] + dp[i - 1][k - arr[i] + ...+dp[i - 1][k - z * arr[i]] ; z为不等式大于等于0 的最大整数
* 根据原来的方程组可知
* dp[i][j] = dp[i - 1][j] + dp[i][j - arr[i]];
*
*/
public static int minCost4(int[] arr,int aim){
if(arr == null || arr.length == 0 || aim < 0){
return 0;
}
int[][] dp = new int[arr.length ][aim + 1];
for(int i = 0;i < arr.length;i++){
dp[i][0] = 1; //第一列初始化
}
for(int j = 1;j * arr[0] <= aim;j++){
dp[0][j * arr[0]] = 1; //第一行初始化
}
for(int i = 1;i < arr.length;i++){
for(int j = 1;j <= aim;j++) {
dp[i][j] = dp[i - 1][j] + (j - arr[i] >= 0 ? dp[i][j - arr[i]] : 0 );
//需考虑 j与arr[i]的大小 括号保证优先级 (+- > 三目运算符)
}
}
return dp[arr.length - 1][aim];
}
/**
* 动态规划
* @param arr 货币面值数组
* @param aim 要找的钱数
* @return 组成的种数
*
* 转移方程
* dp[i][j] = dp[i - 1][j] + dp[i][j - arr[i]];
* 空间优化 使用一维数组
*/
public static int minCost5(int[] arr,int aim){
if(arr == null || arr.length == 0 || aim < 0){
return 0;
}
int[] dp = new int[aim + 1]; //此处不宜通过比较选较小的
for(int j = 0;j * arr[0] <= aim;j++){
dp[j * arr[0]] = 1;//第一行初始化
}
for(int i = 1;i < arr.length;i++){ //从第二行开始
for(int j = 1;j <= aim;j++){
dp[j] = dp[j] + (j - arr[i] >= 0 ? dp[j - arr[i]] : 0 ); //未更新前的dp[j] 就是 dp[i - 1][j]
}
}
return dp[aim];
}
}