zoukankan      html  css  js  c++  java
  • [DP]换钱的方法数

    题目三

    给定数组arr, arr中所有的值都为整数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,在给定一个整数aim代表要找的钱数,求换钱有多少种方法.

    解法一 --暴力递归

    用0张arr[0], 让arr[1...N-1]组成剩下的钱,这样的方法数记为res1
    用1张arr[0], 让arr[1...N-1]组成剩下的钱(aim - arr[0]),方法数记为res2
    用2张arr[0], 让arr[1...N-1]组成剩下的钱(aim - 2 * arr[0]),方法数记为res3
    ...
    从上面可以看出,这是一个递归的过程,总的方法书为res1 + res2 +...

    代码一

    int process1(vector<int> arr, int index, int aim) {
        int res = 0;
        if (index == int(arr.size()))
            res = aim == 0 ? 1 : 0;
        else {
            for (int k = 0; k * arr[index] <= aim; k ++)  //k代表使用arr[index]的张数
                res += process1(arr, index + 1, aim - k * arr[index]);
        }
        return res;
    }
    
    // 暴力递归的方法
    int coins1(vector<int> arr, int aim) {
        if (arr.size() == 0 || aim < 0)
            return 0;
        return process1(arr, 0, aim);
    }
    

    方法二--记忆化搜索

    递归多次调用函数自己,会消耗大量的栈空间.递归的一个初级的优化方法就是用一张表记忆已经计算出的结果,避免重复计算.这张表的维度需要分析递归过程中那些是变量.从上面的代码中可以看到arr是不变的,只有index和aim在递归中是变化的,可以做出相应维度的map表来表示递归的过程.代码如下:

    代码

    
    int process2(vector<int> arr, int index, vector<vector<int> > &mapTable, int aim) {
        int res = 0;
        if (index == int(arr.size()))
            res = aim == 0? 1:0;
        else {
            //mapTable 中的特殊值,0说明mapTable[i][j]没有被计算过,-1表示计算过,但是返回值为0
            int mapValue = 0;
            for (int k = 0; k * arr[index] <= aim; k ++) {
                mapValue = mapTable[index + 1][aim - k * arr[index]];
                if (mapValue != 0)
                    res += mapValue == -1 ? 0 : mapValue;
                else
                    res += process2(arr, index + 1, mapTable, aim - arr[index] * k);
            }
            mapTable[index][aim] = res == 0 ? -1 : res;
        }
        return res;
    }
    
    //记忆化搜索方法
    int coins2(vector<int> arr, int aim) {
        if (arr.size() == 0 || aim < 0)
            return 0;
    
        //记忆搜索表,用于保存递归的中间结果,避免重复计算
        vector<vector<int> > mapTable(arr.size() + 1, vector<int>(aim + 1));
        return process2(arr, 0, mapTable, aim);
    }
    

    方法三--动态规划

    可以直接参考代码,时间复杂度为O(N * aim^2)

    代码

    // 动态规划的方法
    int coins3(vector<int> arr, int aim) {
        int length = arr.size();
        if (length == 0 || aim < 0)
            return 0;
    
        vector<vector<int> > dp(length, vector<int>(aim + 1));
        for (int k = 0; k * arr[0] <= aim ; k ++)  // 初始化第一行
                dp[0][k * arr[0]] = 1;
    
        for (int i = 1; i < length; i ++) // 初始化第一列,0张a[i]可以构成0元钱也是一种方法
            dp[i][0] = 1;
    
        int sum;
        for (int i = 1; i < length; i ++) {
            for (int j = 1; j <= aim; j ++) {
                sum = 0;
                for (int k = 0; k * arr[i] <= j; k++)
                    sum += dp[i - 1][j - k * arr[i]];
                dp[i][j] = sum;
            }
        }
    
        return dp[length - 1][aim];
    }
    

    动态规划--优化

    上面的DP代码最内层的for循环是根据递归中的步骤来表达的.步骤一的方法数为dp[i-1][j],而第二种到第k种情况的方法数累加值其实就是dp[i][j-arr[i]]的值.所以递推方程为:
    dp[i][j] = dp[i-1][j] + dp[i][j-arr[i]]
    这样时间复杂度就简化成了O(N*aim),

    还有进一步的优化就是空间,上面的动态规划空间复杂度都是O(N*aim),通过把二维的dp数组用一维数组来代替,滚动计算能够是空间复杂度降低到O(aim)

  • 相关阅读:
    Vue(小案例_vue+axios仿手机app)_go实现退回上一个路由
    nyoj 635 Oh, my goddess
    nyoj 587 blockhouses
    nyoj 483 Nightmare
    nyoj 592 spiral grid
    nyoj 927 The partial sum problem
    nyoj 523 亡命逃窜
    nyoj 929 密码宝盒
    nyoj 999 师傅又被妖怪抓走了
    nyoj 293 Sticks
  • 原文地址:https://www.cnblogs.com/mooba/p/7473297.html
Copyright © 2011-2022 走看看