📌  相关文章
📜  将 N 写成 K 个非负整数之和的方法数(1)

📅  最后修改于: 2023-12-03 15:09:32.116000             🧑  作者: Mango

将N写成K个非负整数之和的方法数

问题描述

将正整数N分解为K个非负整数之和的不同方案数。例如,把6分解成3个非负整数之和的方案数为10:

  • 0 + 0 + 6
  • 0 + 1 + 5
  • 0 + 2 + 4
  • 0 + 3 + 3
  • 1 + 1 + 4
  • 1 + 2 + 3
  • 2 + 2 + 2
  • 1 + 1 + 1 + 3
  • 1 + 1 + 2 + 2
  • 1 + 2 + 2 + 1
解决方案
动态规划

将问题转化为动态规划问题,令 $f(n, k)$ 表示将正整数 $n$ 分解为 $k$ 个非负整数之和的不同方案数。则有:

$$ f(n,k)=\sum_{i=0}^n f(n-i,k-1) $$

初始化:

$$ \begin{cases} f(n,1)=1 & (n>0) \ f(0,k)=1 & (k\geq 0) \ f(n,k)=0 & (n<0 \text{ or } k<0) \end{cases} $$

代码实现:

def dp(n: int, k: int) -> int:
    f = [[0] * (k + 1) for _ in range(n + 1)]
    for i in range(n + 1):
        f[i][1] = 1
    for i in range(k + 1):
        f[0][i] = 1
    for i in range(1, n + 1):
        for j in range(2, k + 1):
            for x in range(i + 1):
                f[i][j] += f[i - x][j - 1]
    return f[n][k]

时间复杂度:$O(n^2k)$。

递推

考虑记录一个数组 $dp$,其中 $dp[i][j]$ 表示将 $i$ 分解成 $j$ 个非负整数之和的不同方案数。根据 插板法,可以推出递推式:

$$ dp[i][j]=\begin{cases} 1 & (i=0\text{ or }j=0)\ \sum_{k=0}^i dp[k][j-1] & (i>0\text{ and }j>0) \end{cases} $$

代码实现:

def dp(n: int, k: int) -> int:
    dp = [[0] * (k + 1) for _ in range(n + 1)]
    for i in range(n + 1):
        dp[i][0] = 0
        dp[i][1] = 1
    for i in range(k + 1):
        dp[0][i] = 1
    for i in range(1, n + 1):
        for j in range(2, k + 1):
            for x in range(i + 1):
                dp[i][j] += dp[x][j - 1]
    return dp[n][k]

时间复杂度:$O(n^2k)$。

优化

注意到求 $dp[i][j]$ 时,只用到了 $dp[i'][j-1]$ $(i'\leq i)$ 的结果,因此可以将空间复杂度优化为 $O(nk)$。代码实现:

def dp(n: int, k: int) -> int:
    dp = [0] * (n + 1)
    dp[0] = 1
    for j in range(1, k + 1):
        for i in range(1, n + 1):
            for x in range(i + 1):
                dp[i] += dp[x]
        for i in range(1, n + 1):
            dp[i] -= dp[i - 1]
    return dp[n]

时间复杂度:$O(n^2k)$。空间复杂度:$O(nk)$。

总结

本文介绍了将正整数 $N$ 分解为 $K$ 个非负整数之和的不同方案数的三种解法:动态规划、递推、优化版递推。其中动态规划和递推的时间复杂度均为 $O(n^2k)$,而优化版递推的空间复杂度为 $O(nk)$。这三种解法各具特点,可以根据具体问题场景选择合适的解法。