跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

动态规划实战:零钱兑换与爬楼梯中的组合数与排列数

通过零钱兑换 II、组合总和 IV 及爬楼梯三个经典动态规划题目,深入讲解完全背包模型中组合数与排列数的区别。核心在于循环顺序:外层遍历物品内层遍历容量得到组合数,反之得到排列数。文章提供 Java 代码示例及状态转移分析,帮助读者理解计数类 DP 的本质。

魔法巫师发布于 2026/3/29更新于 2026/7/2552 浏览

518. 零钱兑换 II

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

示例

  • 输入:amount = 5, coins = [1, 2, 5]

  • 输出:4

  • 解释:有四种方式可以凑成总金额:

    • 5=5
    • 5=2+2+1
    • 5=2+1+1+1
    • 5=1+1+1+1+1
  • 输入:amount = 3, coins = [2]

  • 输出:0

  • 解释:只用面额 2 的硬币不能凑成总金额 3。

  • 输入:amount = 10, coins = [10]

  • 输出:1

约束条件

  • 0 <= amount (总金额) <= 5000
  • 1 <= coin (硬币面额) <= 5000
  • 硬币种类不超过 500 种
  • 结果符合 32 位符号整数
public int change(int amount, int[] coins) {
    // dp[i] 表示凑成金额 i 的组合数
    int[] dp = new int[amount + 1];
    dp[0] = 1;
    
    // 外层循环遍历硬币(物品)
    for (int i = 0; i < coins.length; i++) {
        // 内层循环遍历金额(背包容量),正序遍历
        for (int j = coins[i]; j <= amount; j++) {
            // 状态转移:使用当前硬币 coins[i] 的组合数
            dp[j] += dp[j - coins[i]];
        }
    }
    return dp[amount];
}

解题思路

dp 数组代表装满背包的方式种类。初始化 dp[0] 设定为 1,这是后续递推的基础。递推公式如下:dp[j] += dp[j - coins[i]]。

这是一个完全背包问题:硬币可以无限使用 → 完全背包求组合数 → 计数类 DP。

:这里求的是(不考虑顺序),不是排列。

关键点
组合
  • 组合数:外层循环硬币、内层循环金额。
  • 排列数:外层循环金额、内层循环硬币。

例如 coins=[1,2],amount=3:

  • 只统计 (1,1,1) 和 (1,2),不会把 (2,1) 算作另一种。

核心区别:组合 vs 排列

组合排列
定义不考虑顺序,{1,2} 和 {2,1} 算同一种考虑顺序,[1,2] 和 [2,1] 算不同
外层循环硬币金额
内层循环金额硬币

图示对比

假设 coins = [1, 2],amount = 3

✅ 外层循环硬币(组合)
for (int coin : coins) { // 先固定硬币 1,再硬币 2
    for (int j = coin; j <= amount; j++) {
        dp[j] += dp[j - coin];
    }
}

执行过程:

步骤操作dp 数组变化含义
初始-[1,0,0,0]只有金额 0 有 1 种方法
用硬币 1更新 dp[1],dp[2],dp[3][1,1,1,1]全用 1:(1,1,1)
用硬币 2更新 dp[2],dp[3][1,1,2,2]加 (1,2)

结果 = 2 种组合:(1,1,1)、(1,2)

关键:硬币 1 处理完才处理硬币 2,不可能出现 2 在 1 前面。

❌ 外层循环金额(排列)
for (int j = 1; j <= amount; j++) { // 先固定金额
    for (int coin : coins) { // 再遍历硬币
        if (j >= coin) dp[j] += dp[j - coin];
    }
}

执行过程:

金额 j遍历硬币计算 dp[j]得到的方式
11,2dp[1]=dp[0]=1(1)
21,2dp[2]=dp[1]+dp[0]=1+1=2(1,1)、(2)
31,2dp[3]=dp[2]+dp[1]=2+1=3(1,1,1)、(1,2)、(2,1)

结果 = 3 种排列:(1,1,1)、(1,2)、(2,1)。

根本原因:状态定义不同

组合(外层硬币)
dp[j] = 用 [已遍历的硬币] 凑成 j 的组合数
  • 第 1 轮:dp[j] = 只用硬币 1 凑成 j
  • 第 2 轮:dp[j] = 用硬币 1 和 2 凑成 j(但 2 只能在 1 后面添加)

强制顺序:硬币必须按 [1,2,5] 的顺序出现,不会出现 2 在 1 前面。

排列(外层金额)
dp[j] = 凑成 j 的排列数(任意硬币都可以作为最后一个)
  • 凑成 3 时,可以是 [1]+ 凑成 2 的任意排列,也可以是 [2]+ 凑成 1 的任意排列
  • 不关心前面用了什么硬币,所以 [1,2] 和 [2,1] 都算。

总结公式

目标代码结构
组合数(本题)for coin : for amount ✅
排列数for amount : for coin ❌

这就是为什么 LeetCode 518(零钱兑换 II)要求组合数,必须用外层循环硬币!


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。

public int combinationSum4(int[] nums, int target) {
    int[] dp = new int[target + 1];
    dp[0] = 1;
    
    // 先遍历背包,再遍历物品
    for (int i = 1; i <= target; i++) {
        for (int j = 0; j < nums.length; j++) {
            if (i >= nums[j]) {
                dp[i] += dp[i - nums[j]];
            }
        }
    }
    return dp[target];
}

解题

本题与上一题目的区别在于本题在求排列数,所以只需要交换遍历顺序即可,先遍历背包,再遍历物品。


57. 爬楼梯

假设你正在爬楼梯。需要 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 阶
import java.util.Scanner;

class climbStairs {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int m, n;
        while (sc.hasNextInt()) {
            // 从键盘输入参数,中间用空格隔开
            n = sc.nextInt();
            m = sc.nextInt();
            
            // 求排列问题,先遍历背包再遍历物品
            int[] dp = new int[n + 1];
            dp[0] = 1;
            
            for (int j = 1; j <= n; j++) {
                for (int i = 1; i <= m; i++) {
                    if (j - i >= 0) {
                        dp[j] += dp[j - i];
                    }
                }
            }
            System.out.println(dp[n]);
        }
    }
}

解题

这是一个完全背包问题 + 排列问题。

目录

  1. 518. 零钱兑换 II
  2. 示例
  3. 约束条件
  4. 解题思路
  5. 核心区别:组合 vs 排列
  6. 图示对比
  7. ✅ 外层循环硬币(组合)
  8. ❌ 外层循环金额(排列)
  9. 根本原因:状态定义不同
  10. 组合(外层硬币)
  11. 排列(外层金额)
  12. 总结公式
  13. 377. 组合总和 IV
  14. 示例
  15. 解题
  16. 57. 爬楼梯
  17. 输入描述
  18. 输出描述
  19. 示例
  20. 解题
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 低代码结合大模型:中小企业半天构建专属SaaS应用路径
  • OpenClaw 智能体框架入门:环境搭建、模型配置与远程访问
  • 16 届蓝桥杯 Java 组省赛真题详解:思路与代码实现
  • Unity 集成 Whisper 实现本地离线语音识别方案
  • Java 初始化 List 的五种常用方式及注意事项
  • C++ STL set 容器详解:特性、常用操作与 multiset 对比
  • TSDK 淘宝开放平台及登录爬虫 SDK
  • Mac 下使用 LLaMA Factory 微调并导出至 Ollama 实战指南
  • AI 与存储的结合:智能存储的实践与挑战
  • 单链表数据结构详解与 C 语言实现
  • 网络安全工程师面试真题汇总:Web 安全、内网渗透与系统加固
  • ELMo 模型详解:上下文感知词向量表示技术
  • Code Llama 7B 模型完整使用指南
  • 2026 年 8 款主流论文 AI 工具横评:从开题到交稿全流程实测
  • VS Code 关闭 Copilot 代码自动补全设置
  • 使用 Hugo、GitHub 与 Cloudflare Pages 搭建个人博客
  • Linux 系统编译安装 GPU 版 LAMMPS 指南
  • Flutter for OpenHarmony network_info_plus 网络扫描与隐私合规适配
  • YOLOv8 集成 Swin Transformer V2 修改与训练实战
  • PCIE044 基于复旦微 JFM7VX690T 的全国产化 FPGA 开发套件

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online