换钱的方法数

题目: 给定数组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];
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值