换钱的方法数

摘自程序员代码面试指南 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=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= 0 ? dp[i][j - arr[i]] : 0;
            }
        }
        return dp[arr.length-1][aim];
    }

5.2 空间上的优化

你可能感兴趣的:(换钱的方法数)