将整数 n 分成 k 份,且每份不能为空,任意两个方案不能相同(不考虑顺序)。
例如: n=7,k=3 ,下面三种分法被认为是相同的。
问有多少种不同的分法, 答案对 109 + 7 取模。
数据范围:
进阶:空间复杂度 ,时间复杂度
展示一下本平民对这道题的思考过程:暴力递归->动态规划->优化动态规划
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int 被划分的数 * @param k int 化成k份 * @return int */ public int divideNumber (int n, int k) { // write code here return recurrent(1, n, k); } /** * 分区调度,对模型按照复杂度划分为partition个工作量相近的分区 * @param prev 前一个划分值 * @param rest 目标数剩余部分 * @param k 剩余划分数 * @return 返回方法数 */ private int recurrent(int prev, int rest, int k) { if(rest == 0 && k == 0) { return 1; // 剩余要凑的数没有了,形成一种方案 } if(rest < prev || k < 0) { return 0; // 剩余数为负数,或者没有划分操作次数了,当前划分方案失效 } // 尝试当前可以划分出来的值,为了保持单调不减,需以从前一个数为下界 int ways = 0; for(int i = prev; i <= rest; i++){ ways += recurrent(i, rest - i, k - 1); } return ways; } }暴力递归仅用来形成一个思路,实际跑的话是会超时的,但我们根据递归的思路可以改出动态规划的版本。
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int 被划分的数 * @param k int 化成k份 * @return int */ public int divideNumber (int n, int K) { // write code here int[][][] dp = new int[n + 1][n + 1][K + 1]; // 只要rest和k归零,就形成一种方案 for(int prev = 1; prev <= n; prev++){ dp[prev][0][0] = 1; } for(int rest = 1; rest <= n; rest++){ for(int prev = 1; prev <= rest; prev++){ for(int k = 1; k <= K; k++){ for(int i = prev; i <= rest; i++){ dp[prev][rest][k] += dp[i][rest - i][k - 1]; } } } } return dp[1][n][K]; } }考虑到题中要求的时间和空间复杂度,再看看能不能删掉一些参数,从时间复杂度O(nk)来看,显然是不能删去k这个参数的。那么再想想rest和prev哪个可以删掉?考虑到某个数n拆分成k份,dp[n][k]会有两种情况:
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int 被划分的数 * @param k int 化成k份 * @return int */ public int divideNumber (int n, int K) { // write code here int[][] dp = new int[n + 1][K + 1]; dp[0][0] = 1; for(int rest = 1; rest <= n; rest++){ for(int k = 1; k <= K && k <= rest; k++){ dp[rest][k] = (dp[rest - 1][k - 1] + dp[rest - k][k]) % 1000000007; } } return dp[n][K]; } }
public static int divideNumber(int n, int k) { final int MOD = (int) (Math.pow(10, 9) + 7); // 定义模数 // 构建二维dp数组 // dp[i][j] 表示将i拆分成j份的方案数 int[][] dp = new int[n + 1][k + 1]; // 初始化dp数组 for (int i = 0; i <= n; i++) { dp[i][0] = 0; // 不能将i拆分成0份 } for (int j = 0; j <= k; j++) { dp[0][j] = 0; // 不能将0拆分成j份 } dp[0][0] = 1; // 将0拆分成0份只有1种方案 // 动态规划填充dp数组 for (int i = 1; i <= n; i++) { for (int j = 1; j <= k; j++) { if (i >= j) { dp[i][j] = (dp[i - 1][j - 1] + dp[i - j][j]) % MOD; } else { dp[i][j] = 0; } } } return dp[n][k]; }
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int 被划分的数 * @param k int 化成k份 * @return int */ int divideNumber(int n, int k) { // write code here int mod = 1e9+7; vector<vector<int>> dp(n+1, vector<int>(k+1, 0)); for(int i=0; i<=k; i++) { dp[i][i] = 1; } for(int i=1; i<=n; i++) { for(int j=1; j<=min(k, i); j++) { dp[i][j] = (dp[i-1][j-1] + dp[i-j][j]) % mod; } } return dp[n][k]; } };
对于dp[i][j]代表i分为j份。分为以下两类:
function divideNumber( n , k ) { // write code here const dp = Array.from(new Array(n+1), ()=>new Array(k+1).fill(0)) dp[0][0] = 1 for (let i=1;i<=n;++i){ for (let j=1;j<=n;++j){ if ( i-j>=0){ dp[i][j]=dp[i-j][j]+dp[i-1][j-1]; } } } return dp[n][k] }
public class Solution { private static final int MOD = 1_000_000_007; private int[][] memo; public int divideNumber(int n, int k) { this.memo = new int[n + 1][k + 1]; return dfs(n, k); } private int dfs(int n, int k) { if (n == 0 || n < k) { return 0; } if (k == 1 || k == n) { return 1; } if (memo[n][k] > 0) { return memo[n][k]; } return memo[n][k] = (dfs(n - 1, k - 1) + dfs(n - k, k)) % MOD; } }
class Solution { public: int divideNumber(int n, int k) { // write code here int dp[7][201]; // k, n for(int k=1;k<=6;k++){ for(int n=0;n<k;n++ ){ dp[k][n]=0; } dp[k][k]=1; } for(int n=1;n<=200;n++) dp[1][n]=1; for(int k=2;k<=6;k++){ for(int n=k+1;n<=200;n++){ dp[k][n]=dp[k-1][n-1]+dp[k][n-k]; // 分为两种情况: // 1.包含 1(即其中一份只分了1) 等价于 dp[k-1][n-1] // 2.都为 大于等于2. 等价于 dp[k][n-k] } } return dp[k][n]; } };
/** * dp[i][j]将数i分为j分的分法 * 拆分的结果不包含1的情况:如果不包含1,我们把n拆分成k块时可以看做先将每一块加上个1, * 则n还剩余n-k,即f(n-k,k) * 拆分的结果包含1的情况:那么就直接选一个1,即f(n-1,k-1)。 * dp[i][j]=dp[i-j][j]+dp[i-1][j-1] * @param n * @param k * @return */ public int divideNumber (int n, int k) { // write code here int[][] dp = new int[n+1][k+1]; dp[0][0]=1; for (int i=1;i<=n;i++){ for (int j=1;j<=k&&j<=i;j++){ dp[i][j]=dp[i-j][j]+dp[i-1][j-1]; } } return dp[n][k]; }
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int 被划分的数 * @param k int 化成k份 * @return int */ public int divideNumber (int n, int k) { // write code here int[][]dp=new int[n+1][k+1]; dp[0][0] = 1; for(int i =0;i <=n; i++){ for(int j =1; j<=i && j <= k;j++){ dp[i][j] = dp[i-1][j-1] + dp[i-j][j]; } } return dp[n][k]; } }