题目描述

✅ LCR 104. 组合总和 Ⅳ

image-20260929004425666

image-20260929004425667

题意分析

从互不相同的正整数 nums 中取数,每个数可以使用任意多次,统计总和恰好为 target 的有序序列数量。顺序不同就算不同方案,无法凑出时返回 0。

题目保证最终答案在 32 位整数范围内,要求精确计数,不需要取模。因为所有数都是正数,序列每增加一项,总和都会变大。

解法:按末尾元素统计有序方案

核心思路

[!blue]

定义 dp[i] 为总和恰好为 i 的有序序列数。将这些序列按最后一个数 num 分类:去掉最后一项后,前缀的和就是 i - num;反过来,给任意这样的前缀追加 num,都能得到一个和为 i 的序列。因此对所有 num <= i,累加 dp[i - num] 即可。

每个序列的最后一项唯一,且候选值互不相同,各类计数不会重叠。初始化 dp[0] = 1,表示唯一的空序列;当 i == num 时,从这个空序列追加一项,就能正确计入单元素方案。

正整数保证 i - num < i,所以从小到大枚举目标和时,全部前驱都已完成。外层是金额 i,内层尝试每个末项;前驱本身已允许使用所有候选值,因此既支持重复取数,也保留了各种不同顺序。

若交换为元素外层、金额内层,就会按固定的元素处理顺序构造方案,同一批元素的不同排列被合并。区别在于状态包含的选择范围,而不在于分类用首项还是末项:按首项分类,同样能推导出有序序列的这条递推。

对能够追加某个后缀到达 target 的中间状态,它的每个前缀方案都能接上同一个后缀,得到不同的目标序列,所以这些相关状态的计数不会超过最终答案。其他状态不存在通向目标的转移链,即使计数超出整数范围,也不会影响目标的计算,现有代码无需擅自取模。

负数进阶:若允许负数,按金额递增计算就失去了依赖顺序,而且可能存在总和为 0 的循环。若目标可达且存在这种循环,就能反复插入循环,产生无穷多种序列;含负数本身并不意味着所有输入都必然无穷。可以增加最大长度 L 的限制,令 f[len][sum] 表示长度恰为 len、总和为 sum 的方案数,从 f[0][0] = 1 按长度递推 f[len][sum] = Σ f[len - 1][sum - num],最后累计长度不超过 L 的目标状态。和可能为负,可用哈希表保存每层的有限状态。

解题步骤

  1. 创建长度为 target + 1 的计数数组,设置 dp[0] = 1。
  2. 从 1 到 target 递增枚举当前总和。
  3. 遍历所有候选 num,若不超过当前总和,就累加对应的前驱计数。
  4. 返回 dp[target],不可达时它自然为 0。

代码实现

class Solution {

    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];

        // 空序列是凑出 0 的唯一方案。
        dp[0] = 1;

        // 容量在外层、元素在内层:按「序列末位」分类,统计的是排列数。
        for (int i = 1; i <= target; ++i) {
            for (int num : nums) {
                if (i >= num) {
                    dp[i] += dp[i - num];
                }
            }
        }

        return dp[target];
    }
}
func combinationSum4(nums []int, target int) int {
    dp := make([]int, target+1)
    // 空序列是凑出 0 的唯一方案。
    dp[0] = 1
    // 容量在外层、元素在内层:按「序列末位」分类,统计的是排列数。
    for i := 1; i <= target; i++ {
        for _, num := range nums {
            if i >= num {
                dp[i] += dp[i-num]
            }
        }
    }
    return dp[target]
}

复杂度分析

  • 时间复杂度:$O(nT)$,其中 $n$ 为候选数个数、$T$ 为目标和,每个目标检查全部候选值。
  • 空间复杂度:$O(T+1)$,保存各个总和的计数。

关键点总结

[!green]

  • 每个序列唯一对应“较短序列加最后一项”,这是计数不重不漏的依据。
  • dp[0] = 1 表示空前缀,不是把不可达金额记成一种方案。
  • 候选值互不相同,无需去重;同一个值可以在序列中反复出现。
  • 负数进阶需要额外保证方案有限,限制序列长度后再按长度分层。

易错点总结

[!yellow]

  • 将元素放在外层会改变计数含义,不能与本题金额外层的写法直接互换。
  • 按首项分类仍可统计有序序列,不能把它误认为无序组合。
  • 只有 i >= num 时才能读取 dp[i - num]。
  • 不能因遇到大计数就对任意模数取模,题目要求的是精确答案。

相似题目

题目 难度 关联与区别
39. 组合总和 中等 原题不区分组合内顺序,本题不同排列顺序分别计数,递推循环顺序需体现区别。
518. 零钱兑换 II 中等 同样允许重复取数,硬币组合通常不计排列,本题按最后一项枚举有序方案。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/27558928
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!