美文网首页
换钱的方法数

换钱的方法数

作者: 柴崎越 | 来源:发表于2019-02-24 17:46 被阅读0次

    摘自程序员代码面试指南 IT名企算法与数据结构题目最优解 [左程云著][电子工业出版社][2015.09][513页][13859131]

    一 问题

    给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数求换钱有多少种方法?
    举例:
    arr=[5,10,25,1],aim=0。组成0元的方法有1种,就是所有面值的货币都不用。所以返回1。
    arr=[5,10,25,1],aim=15.
    组成15元的方法有6种,分别为3张5元、1张10元+1张5元、1张10元+5张1元、10张1元+1张5元、2张5元+5张1元和15张1元。所以返回6.
    arr=[3,5],aim=2。任何方法都无法组成2元。所以返回0.

    二 暴力递归

    2.1 图片.png

    base case 递归跳出的条件,当走到最后一个数时(index==数组的长度时)
    aim=0

    2.2代码实现

    2.2.1 正推

    public static int coins1(int[] arr,int aim)
        {
            if(arr==null||arr.length==0||aim<0)
            {
                return 0;
            }
            return process1(arr,0, aim);
        }
        public static int process1(int[] arr,int index,int aim)
        {
            int res=0;
            //base case 
            if(index==arr.length)
            {
                res=aim==0?1:0;//当走到base case时,如果aim==0了,记为1次
            }else
            {    
                for(int i=0;arr[index]*i<=aim;i++)
                {
                    res+=process1(arr,index+1,aim-arr[index]*i);
                }
            }
            return res;
        }
    

    2.2.2 反推

    public static int coinOther(int[] arr,int aim)
        {
            if(arr==null||arr.length==0||aim<0)
            {
                return 0;
            }
            return processOther(arr,arr.length-1, aim); 
        }
        public static int processOther(int[] arr,int index,int aim)
        {
            int res=0;
            if(index==-1)
            {
                res=aim==0?1:0;
            }else
            {
                for(int i=0;arr[index]*i<=aim;i++)
                {
                    res+=processOther(arr, index-1, aim-arr[index]*i);
                }
            }
            return res;
        }
    

    2.3 时间复杂度分析

    最差情况O(aim^N)

    三 优化之记忆搜索

    3.1 分析

    递归存在着多种重复的情况,例如 图片.png
    优化方式process中的arr是一直不变化的,所以p(index,aim)表示一个递归过程,可以准备一个map,记录递归过程,有重复的,可以直接拿出来使用 图片.png

    3.2 代码实现

    //优化一之记忆搜索
        public static int coins(int[] arr,int aim)
        {
            if(arr==null||arr.length==0||aim<0)
            {
                return 0;
            }
            //map[i][j]表示
            int[][] map=new int[arr.length+1][aim+1];
            return process2(arr, 0, aim, map);
        }
        public static int process2(int[] arr,int index,int aim,int[][] map)
        {
            int res=0;
            if(index==arr.length)
            {
                res=aim==0?1:0;
            }
            else
            {
                int mapValue=0;
                for(int i=0;arr[index]*i<=aim;i++)
                {   
                    mapValue=map[index+1][aim-arr[index]*i];
                    if(mapValue!=0)
                    {
                        res+=mapValue==-1?0:mapValue;
                    }else{
                        res+=process2(arr, index+1, aim-arr[index]*i, map);
                    }
                }
            }
    //将此过程记录下来
            map[index][aim]=res==0?-1:res;
            return res;
        }
    

    3.3 时间复杂度分析

    四 动态规划

    4.1 由暴力递归代码改出动态规划

    图片.png

    process1(int[] arr,int index,int aim)中发生变化的只有index和aim,所有创造一个以index和aim为单位的二维数组.代码如下

        public static int coins3(int[] arr,int aim)
        {
            if(arr==null||arr.length==0||aim<0)
            {
                return 0;
            }
            //arr.length包括-1,列包括0
            int[][] dp=new int[arr.length][aim+1];
            //第一列为0
            for(int i=0;i<arr.length;i++)
            {
                dp[i][0]=1;
            }
            for(int j=1;arr[0]*j<=aim;j++)
            {
               dp[0][arr[0]*j]=1;               
            }
            int num=0;
            for(int i=1;i<arr.length;i++)
            {
                for(int j=1;j<=aim;j++)
                {
                    num=0;
                    for(int k=0;j-arr[i]*k>=0;k++)
                    {
                        num+=dp[i-1][j-arr[i]*k];
                    }
                    dp[i][j]=num;
                }
            }
            return dp[arr.length-1][aim];
        }
    

    4.2 代码含义

    图片.png 图片.png
    图片.png
    图片.png
    图片.png

    4.3 时间复杂度估计 图片.png

    五,动态规划时间和空间的优化

    5.1时间上的优化

    图片.png

    注:以上截图有小错误,以此截图为准

    public static int coins4(int[] arr,int aim)
        {
            if(arr==null||arr.length==0||aim<0)
            {
                return 0;
            }
            //arr.length包括-1,列包括0
            int[][] dp=new int[arr.length][aim+1];
            //第一列为0
            for(int i=0;i<arr.length;i++)
            {
                dp[i][0]=1;
            }
            for(int j=1;arr[0]*j<=aim;j++)
            {
               dp[0][arr[0]*j]=1;               
            }
            int num=0;
            for(int i=1;i<arr.length;i++)
            {
                for(int j=1;j<=aim;j++)
                {
                    dp[i][j] = dp[i - 1][j];
                    dp[i][j] += j - arr[i] >= 0 ? dp[i][j - arr[i]] : 0;
                }
            }
            return dp[arr.length-1][aim];
        }
    

    5.2 空间上的优化

    相关文章

      网友评论

          本文标题:换钱的方法数

          本文链接:https://www.haomeiwen.com/subject/fdrbyqtx.html