将整数 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]
} class Solution: def divideNumber(self , n: int, k: int) -> int: mod = 10**9 + 7 # dp[i][j] 表示将整数i分成j份 dp = [[0]*(k+1) for _ in range(n+1)] for i in range(k+1): dp[i][i] = 1 for i in range(1,n+1): for j in range(1,min(i,k)+1): dp[i][j] = (dp[i-j][j] + dp[i-1][j-1]) % mod 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];
}
}