对于dp算法“装满背包有多少中方式”

1、求组合

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

class Solution {
    public int change(int amount, int[] coins) {
        //dp[i][j] 表示用 0 - (i - 1)的物品装满容量为j的背包有 多少种方式

        //dp[i][j] = dp[i - 1][j - conis[i]] + dp[i - 1][j]

        //初始化
        int length1 = coins.length;
        int[][] dp = new int[length1][amount + 1];

        for(int h = 0; h < length1; h++){
            dp[h][0] = 1;
        }
        for(int l = coins[0]; l <= amount; l++){
            dp[0][l] += dp[0][l - coins[0]];
        }

        //int j = 1; j <= amount; j++
        //int i = 1; i < length1; i++

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

    }
}

2、求排列

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。
class Solution {
    public int combinationSum4(int[] nums, int target) {
        //这道题本质上并不是背包问题
        //数学推导 dp[i][j] 表示分别用nums[0 - i-1]作为排列最后一个元素求出j的个数
        //辅助函数P[i][j] 表示 用 nums[i] 作为排列最后一个元素求出j的个数
        //所以dp[i][j] = P[i][j] + dp[i - 1][j]
        //考虑P[i][j]内的所有排列,由于其末尾元素都是nums[i−1],前面的元素没有限制(即nums内可选的所有元素),那么我们把这些排列的末尾元素全部移除,就构成了一组新的排列。
        //P[i][j] = dp[n][j - nums[i]]
        //所以递推公式为 dp[i][j] = dp[n][j - nums[i]] + dp[i - 1][j](n为nums大小)

        int length1 = nums.length;
        int[][] dp = new int[length1 + 1][target + 1];

        //初始化
        //为了满足题目要求

        dp[0][0] = 1;

        //根据递推公式确认遍历顺序
        for(int j = 0; j <= target; j++){
            for(int i = 1; i <= length1; i++){
                //排列组合数可能超过int范围
                if(j < nums[i - 1] || dp[i-1][j] > Integer.MAX_VALUE - dp[length1][j - nums[i - 1]]){
                    dp[i][j] = dp[i - 1][j];
                }
                else{
                    dp[i][j] = dp[length1][j - nums[i - 1]] + dp[i - 1][j];
                }
            }
        }
        return dp[length1][target];

    }
}

区别:递推公式不同 遍历顺序也不同

  • 完全背包问题(第一个问题)

    • 在这个问题中,i 表示你在考虑的硬币种类。你希望逐个考虑硬币,并确保每种硬币的组合数都能被累加进来。
    • 所以,递推公式中的 i 是用于遍历所有硬币的种类。
  • 排列问题(第二个问题)

    • 在排列问题中,你需要考虑每个元素作为排列的最后一个元素,这意味着你可以在任意时刻选择整个数组中的任意元素来组成排列。这里 length1 表示可以使用整个数组 nums 来构造排列。
    • 因此在计算递推公式时,需要允许选择 nums 中所有的元素,即 dp[length1][j - nums[i - 1]] 代表可以用所有的 nums 来继续组合。
       
    • 组合问题不关心顺序,因此可以用递推公式 dp[i][j] = dp[i][j - coins[i]] + dp[i - 1][j],因为它只考虑选择或不选择某个元素,不需要处理顺序问题。
    • 排列问题关心顺序,因此需要遍历每个元素作为排列的最后一个元素,使用 dp[j] += dp[j - nums[i]] 来正确处理排列的不同顺序。


      通俗的理解就是:在遍历时候你将大的数值放在集合的后面,和放在集合的前面是不是一样的。

      1、在组合里是一样的,然后配合先遍历物品的话就能保住同一种组合方式(组合内的数值和数值的数量一样的情况)只会出现一次,而且是下标小的数在结果集合里越靠前。先遍历物品,保证了同一元素只能在当前及后续的容量计算中出现,不会重新被选择
    • 2、在排列里是不一样的,所以如果按照组合的递推公式累加,会丢掉后续的大的下标i对应的数值出现在结果集合前面的情况。先遍历容量,在遍历物品保证每一个容量都要尝试将所有可能的元素作为排列的最后一个元素
      这样才能保证,每一种容量考虑到所有的排列。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值