动态规划-完全背包问题


1. 零钱兑换(322)

题目描述:
在这里插入图片描述
状态表示:
这题和前面博客总结的完全背包模板题是类似的,使用二维数组dp[i][j]表示在前i个硬币当中选择,使得金额满足j,不同的硬币可以重复选取,求得满足此条件得最小硬币个数。
状态转移方程:
状态转移方程也是和完全背包问题分析的方式一致,对于第i个硬币,分为不选和选多个的情况。首先对于不选第i个硬币的情况,dp[i][j]=dp[i-1][j]。对于选择一个i硬币的情况,dp[i][j]=dp[i-1][j-coins[i]]+1。对于选择两个i硬币的情况,dp[i][j]=dp[i-1][j-2* coins[i]]+2。你选择k个i硬币,dp[i][j]=dp[i-1][j-k* coins[i]]+k。此时要求的dp[i][j]就是得求出它们的最小值,我们可以用数学推导的方式使用dp[i][j-coins[i]]+1来表示后续的情况(具体过程可以参考前一个完全背包模板题的那个博客),那么dp[i][j]=max(dp[i-1][j],dp[i][j-coins[i]]+1),当然这里的表达式必须要经过j-coins[i]>=0的判断。
初始化:
为了方便运算以及防止越界,给dp数组加上第0行和第0列,对于第0列我们还是一样直接加入运算不去初始化。对于dp[0][0],此时表示0个硬币去满足0个金额,此时满足并且硬币数为0,所以dp[0][0]=0。对于第0行的其它位置,此时都是0个硬币要去满足不同的不等于0的金额,这肯定是满足不了的,也就是题目中的硬币数为-1的情况,此时为了不让这些位置的值影响运算,将这些位置赋一个很大的值。
填表顺序:
从上到下,从左至右。
返回值:
返回值就是dp[n][amount],但是此时要进行一个判断如果dp[n][amount]的值非常大,那么此时就返回-1,否则就返回dp[n][amount]。
代码如下:

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int INF = 0x3f3f3f3f;
        int[][] dp = new int[n + 1][amount + 1];
        for (int i = 1; i <= amount; i++) {
            dp[0][i] = INF;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= amount; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= coins[i - 1]) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - coins[i - 1]] + 1);

                }

            }
        }

        return dp[n][amount] >= INF ? -1 : dp[n][amount];

    }
}

代码运行效率:
在这里插入图片描述

优化后代码如下:

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int INF = 0x3f3f3f3f;
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; i++) {
            dp[i] = INF;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = coins[i - 1]; j <= amount; j++) {
                    dp[j] = Math.min(dp[j], dp[j - coins[i - 1]] + 1);
            }
        }

        return dp[amount] >= INF ? -1 : dp[amount];

    }
}

优化后代码运行效率:
在这里插入图片描述

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)
优化后空间复杂度:O(N)

2. 零钱兑换 II(518)

题目描述:
在这里插入图片描述

状态表示:
还是和之前的 题目类似,使用二维数组dp[i][j]表示在前i个硬币进行选择,要求满足j这样的金额时有多少种选法。
状态转移方程:
分析过程也和前面的完全背包问题类似,就是当选择第i个硬币时,可以不选,那么dp[i][j]=dp[i-1][j]。也可以选择一个i硬币,此时dp[i][j]=dp[i-1][j-coin[i-1]],选择两个i硬币,此时dp[i][j]=dp[i-1][j-2* coins[i]],后面的情况也是类似的,这些情况的总和等于dp[i][j-coins[i]].所以最终结果应该将得到的两项加起来。dp[i][j]=dp[i-1][j]+dp[i][j-coins[i]].
初始化:
给dp数组加上第0行和第0列,第0列直接加入运算不需要初始化。dp[0][0]此时硬币数为0并且需要满足的金额为0,此时有一种选法,因此dp[i][j]=1.第0行的其它位置硬币数为0但是要求满足不同的金额,此时没有选法,因此都赋为0即可。
填表顺序:
从上到下,从左至右。
返回值:
dp[n][amount],n为coins数组的长度,amount为金额的总数。
代码如下:

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        dp[0][0] = 1;

        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= amount; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j - coins[i - 1] >= 0) {
                    dp[i][j] += dp[i][j - coins[i - 1]];
                }
            }
        }
        return dp[n][amount];
    }
}

代码运行效率:
在这里插入图片描述

优化后代码如下:

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[] dp = new int[amount + 1];
        dp[0] = 1;

        for (int i = 1; i <= n; i++) {
            for (int j = coins[i-1]; j <= amount; j++) {      
                    dp[j] += dp[j - coins[i - 1]];  
            }
        }
        return dp[amount];
    }
}

优化后代码运行效率:
在这里插入图片描述
题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)
优化后空间复杂度:O(N)

3. 完全平方数(279)

题目描述:
在这里插入图片描述

状态表示:
设置二维数组dp[i][j]表示在前i个数字的平方和中进行选择,使得数值总和等于j时的最小数字数量。
状态转移方程:
完全背包问题的状态转移方程的分析都是类似的,对于dp[i][j]来说,如果我们不选择第i个数字的平方和,那么此时dp[i][j]=dp[i-1][j].当我们选择一个第i个数字的平方和时,dp[i][j]=dp[i-1][j-i* i]+1,当我们选择两个第i个数字的平方和时,dp[i][j]=dp[i-1][j-2* i* i],选择三个第i个数字的平方和也是类似的…为了去表达这些情况也是类似的,我们是直接使用数学方式推导(具体过程看模板题博客)dp[i][j-ii]+1。因为求得是最小数字量,所以dp[i][j]=min(dp[i-1][j],dp[i][j-ii]+1).
初始化:
给dp数组加上第0行和第0列,对于第0列我们直接加入运算不去初始化,对于dp[0][0],此时0个数字,0数值总和要求,是可以构成这种情况的,但是数字数量为0所以直接赋为0。对于第0行剩余内容,因为此时没有数字并且要满足不同的数值和,这些情况是不可能存在的,为了避免它们的值影响后续运算,将它们的值赋为无穷大。
填表顺序:
从上到下,从左至右。
返回值:
对于要满足的数字平方和n,其中添加的数字不可能会超过n的开方,因此不仅我们的运算的外层循环的条件是i<=n的开方,我们的返回值也是dp[n的开方][n]。
代码如下:

class Solution {
    public int numSquares(int n) {
        int[][] dp = new int[(int) Math.sqrt(n) + 1][n + 1];
        int INF = 0x3f3f3f3f;
        for (int i = 1; i <= n; i++) {
            dp[0][i] = INF;
        }
        for (int i = 1; i <= (int) Math.sqrt(n); i++) {
            for (int j = 0; j <= n; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= i * i) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - i * i] + 1);
                }
            }
        }

        return dp[(int) Math.sqrt(n)][n] >= INF ? 0 : dp[(int) Math.sqrt(n)][n];
    }
}

代码运行效率如下:
在这里插入图片描述

优化后代码如下:

class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        int INF = 0x3f3f3f3f;
        for (int i = 1; i <= n; i++) {
            dp[i] = INF;
        }
        for (int i = 1; i <= (int) Math.sqrt(n); i++) {
            for (int j = i * i; j <= n; j++) {         
                dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
            }
        }

        return dp[n] >= INF ? 0 : dp[n];
    }
}

优化后代码运行效率如下:
在这里插入图片描述

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)
优化后空间复杂度:O(N)

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值