代码随想录算法训练营第三十二天:零钱兑换II,组合综合IV,爬楼梯(进阶版)
2026/6/8 6:51:16 网站建设 项目流程

518.零钱兑换II

文章讲解/视频讲解

题目描述:
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 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

注意,你可以假设:

  • 0 <= amount (总金额) <= 5000
  • 1 <= coin (硬币面额) <= 5000
  • 硬币种类不超过 500 种
  • 结果符合 32 位符号整数

思路:
本题就很像之前做过的“目标和”,都是给出背包容量,求装满背包的方法数。但是“目标和”每个元素只能使用一次,前面我们介绍过了这是01背包的特征。而本题每种面额的硬币都有无穷个,所以这就是之前提到过的完全背包,一样来写动态规划五部曲

1.确定dp数组及其下标含义:dp[i][j]:使用 下标为[0, i]的coins[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种组合方法。

2.确定递推公式:完全背包递推公式与01背包的区别在于:

01背包递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]),01背包如果放了物品i,再取就只能拿物品0到物品i - 1这个范围了,物品i拿不了。

而完全背包公式为:dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]),因为有无限个物品,我拿完物品i下一次还是能从物品0到物品i的区间里拿。

本题的一维dp数组递推公式为dp[j] += dp[j - coins[i]],与目标和一致,之前讲过这里不再过多赘述

3.dp数组的初始化:我们要关注的就是dp[0],装满背包容量为0的方法是1,即不放任何物品,dp[0] = 1

4.确定遍历顺序:完全背包的两个for循环随便先后顺序都可以,但是本题不行!因为本题本质和元素凑成的顺序没多大关系,本题描述也是求组合数。假设存在结果221和212,如果求的组合数,那就只有一种,如果是求排列数那么这可就算两种排列了。

我们先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。

代码如下:

for (int i = 0; i < coins.size(); i++) { // 遍历物品 for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量 dp[j] += dp[j - coins[i]]; } }

假设:coins[0] = 1,coins[1] = 5。

那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

所以这种遍历顺序中dp[j]里计算的是组合数!

如果把两个for交换顺序,代码如下:

for (int j = 0; j <= amount; j++) { // 遍历背包容量 for (int i = 0; i < coins.size(); i++) { // 遍历物品 if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]]; } }

背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。

此时dp[j]里算出来的就是排列数!

所以一定是先遍历物品,再遍历背包容量,想不明白就打印数组自己看看

5.举例推导dp数组

输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下:

最后红色框dp[amount]为最终结果。

代码示例:

function change(amount: number, coins: number[]): number { const dp:number[] = new Array(amount + 1).fill(0) dp[0] = 1 for(let i = 0 ; i < coins.length; i++){ for(let j = coins[i]; j <= amount; j++){ dp[j] += dp[j - coins[i]] } } return dp[amount] };

377.组合总和IV

文章讲解/视频讲解

题目描述:

难度:中等

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

  • nums = [1, 2, 3]
  • target = 4

所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

思路:

与上题一样的思路,但是换成了求排列,虽然本题题干说的是求组合,但是我们对组合的定义其实是不强调顺序,即1,5和5,1算同一个组合,但其实算两个不同的排列。

1.确定dp数组及其下标含义:dp[i]:凑成目标正整数为i的排列个数为dp[i]

2.确定递推公式:之前说过了,只要是求装满背包有几种方法,一般都是用:dp[i] += dp[i - nums[j]

3.dp数组如何初始化:dp[0]一定要初始化为1,否则递归其他dp[i]就没有数值基础了,其他全部初始化为0,避免影响dp[i]累加

4.确定遍历顺序:上一题求的是组合,我们先遍历物品再遍历背包,本题求的是排列,所以是先遍历背包再遍历物品。

5.举例来推导dp数组

我们再来用示例中的例子推导一下:

代码示例:

function combinationSum4(nums: number[], target: number): number { const dp:number[] = new Array(target + 1).fill(0) dp[0] = 1 for(let i = 1; i <= target; i++){ for(let j = 0; j < nums.length; j++){ if(i >= nums[j]){ dp[i] += dp[i - nums[j]] } } } return dp[target] };

爬楼梯(进阶)

文章讲解

题目描述:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

输入描述:输入共一行,包含两个正整数,分别表示n, m

输出描述:输出一个整数,表示爬到楼顶的方法数。

输入示例:3 2

输出示例:3

提示:

当 m = 2,n = 3 时,n = 3 这表示一共有三个台阶,m = 2 代表你每次可以爬一个台阶或者两个台阶。

此时你有三种方法可以爬到楼顶。

  • 1 阶 + 1 阶 + 1 阶段
  • 1 阶 + 2 阶
  • 2 阶 + 1 阶

思路:

其实我们之前就写过一道爬楼梯了,力扣上那道是一次只能爬一节或两节,本题可以爬1到m任意节,其实还是一个完全背包。每次能爬的阶数就是物品,楼顶就是背包容量,我这次跳了一节,下次还能再跳一次一节,也就是物品可以重复使用。而问跳到楼顶有几种方法,其实不就是问装满背包有几种方法吗?

1.确定dp数组及其下标含义:dp[i]:爬到i个台阶的楼顶,有dp[i]种方法

2.确定递推公式:求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];本题比较特殊,dp[i]的来源是dp[i -1],dp[i -2]......,也就是dp[i - j],所以递推公式为dp[i] += dp[i - j]

3.dp数组如何初始化:依旧是dp[0]为1,其他非零下标全是1

4.确定遍历顺序:这里先上一节再上三节,和先上三节再上一节,最后都上了四节,但是算两种不同的方法,前面提到过,这种我们称作排列,也就是背包容量在外面循环,物品在内循环。

5.举例推导dp数组:与上题基本一致

代码示例:

var climbStairs = function (n: number): number { let dp: number[] = new Array(n + 1).fill(0); dp[0] = 1; for (let j = 1; j <= n; j++) {//遍历背包 for (let i = 1; i <= 2; i++) {//遍历物品 if (j - i >= 0) dp[j] = dp[j] + dp[j - i]; } } return dp[n]; }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询