算法日记day 37(动归之零钱兑换2|组合问题4)

一、零钱兑换2

题目:

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

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

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

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

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

输入:amount = 10, coins = [10] 
输出:1

思路:

首先明确dp数组的含义,本题中dp数组的意思为,装满容量为 j 的背包有dp[j]种方法,在遍历时保证所有情况均已经被遍历到,因此有递推式 

                                                     dp[j] += dp[j-coins[i]]

代码:

public int change(int amount, int[] coins) {
    // 创建一个一维动态规划数组 dp
    // dp[i] 表示组成金额 i 的不同硬币组合的数量
    int[] dp = new int[amount + 1];
    
    // 初始化 dp 数组的第一个元素为 1
    // dp[0] = 1 表示组成金额 0 的组合只有一种,即不使用任何硬币
    dp[0] = 1;

    // 遍历每一种硬币
    for (int i = 0; i < coins.length; i++) {
        // 对于每种硬币,从硬币的面额到总金额进行更新
        for (int j = coins[i]; j <= amount; j++) {
            // dp[j] 表示组成金额 j 的组合数量
            // dp[j - coins[i]] 表示组成金额 j - coins[i] 的组合数量
            // 将 dp[j - coins[i]] 加到 dp[j] 上,以包括当前面额的硬币
            dp[j] += dp[j - coins[i]];
        }
    }
    
    // 返回组成金额 amount 的不同组合数量
    return dp[amount];
}
  1. 初始化 dp 数组

    • dp[i] 表示组成金额 i 的不同组合数量。dp 数组的长度为 amount + 1,因为我们需要考虑从金额 0 到 amount 的所有情况。
    • dp[0] 被初始化为 1,表示组成金额 0 只有一种方式,即不使用任何硬币。
  2. 遍历每种硬币

    • 外层循环遍历每种硬币的面额 coins[i]
    • 内层循环从硬币的面额 coins[i] 开始遍历到总金额 amount。这确保每种面额的硬币都会被考虑到。
  3. 更新 dp 数组

    • 对于每个金额 jdp[j] 更新为 dp[j] + dp[j - coins[i]]。这里 dp[j - coins[i]] 表示在添加当前面额的硬币前,已经能够组成金额 j - coins[i] 的组合数量。加上当前面额的硬币后,金额 j 的组合数量就增加了 dp[j - coins[i]]
    • 这种更新方式确保每种硬币的所有组合都被计算到。
  4. 返回结果

    • dp[amount] 最终包含了组成金额 amount 的不同硬币组合的数量。

二、组合问题4 

题目:

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

题目数据保证答案符合 32 位整数范围。

示例 1:

输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

输入:nums = [9], target = 3
输出:0

思路:

与零钱兑换2类似,但是零钱兑换所求得的是组合数,而本题求的是排列数,因此在代码上略有差别

代码:

public int combinationSum4(int[] nums, int target) {
    // 创建一个一维动态规划数组 dp
    // dp[i] 表示组成金额 i 的不同组合的数量
    int[] dp = new int[target + 1];
    
    // 初始化 dp 数组的第一个元素为 1
    // dp[0] = 1 表示组成金额 0 的组合只有一种,即不使用任何数字
    dp[0] = 1;

    // 遍历从 0 到 target 的每个金额 i
    for (int i = 0; i <= target; i++) {
        // 遍历数组 nums 中的每一个数
        for (int j = 0; j < nums.length; j++) {
            // 如果当前金额 i 大于等于 nums[j],说明可以使用 nums[j] 来组成金额 i
            if (i >= nums[j]) {
                // dp[i] 加上 dp[i - nums[j]] 的值
                // dp[i - nums[j]] 表示组成金额 i - nums[j] 的组合数量
                // 这样 dp[i] 就表示在已有的组成金额 i 的组合上加上一个 nums[j]
                dp[i] += dp[i - nums[j]];
            }
        }
    }
    
    // 返回组成金额 target 的不同组合数量
    return dp[target];
}
  1. 初始化 dp 数组

    • dp[i] 表示组成金额 i 的不同组合的数量。dp 数组的长度为 target + 1,因为我们需要计算从金额 0 到 target 的所有情况。
    • dp[0] 被初始化为 1,表示组成金额 0 只有一种方式,即不使用任何数字。
  2. 遍历每个金额 i

    • 外层循环遍历每个金额 i 从 0 到 target
    • 对于每个金额 i,内层循环遍历 nums 数组中的每个数字 nums[j]
  3. 更新 dp 数组

    • 如果当前金额 i 大于等于 nums[j],则 dp[i] 可以通过添加一个 nums[j] 来更新。具体来说,dp[i] 将加上 dp[i - nums[j]] 的值。
    • dp[i - nums[j]] 表示组成金额 i - nums[j] 的组合数量。加上当前数字 nums[j] 后,金额 i 的组合数量就增加了 dp[i - nums[j]]
  4. 返回结果

    • 最终,dp[target] 包含了组成目标金额 target 的不同组合的总数。

今天的学习就到这里 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值