数的划分

问题描述:将整数n分成k份,且每份不能为空,任意两个方案不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种分法被认为是相同的。
1,1,5;
1,5,1;
5,1,1;
问有多少种不同的分法。
输入:n,k ( 6 < n ≤ 200,2 ≤ k ≤ 6 )
输出:一个整数,即不同的分法。
示例
输入:7,3
返回值:4
说明:整数7分为3份有4种办法:[1,1,5][1,2,4][1,3,3][2,2,3]    

方法一

思路分析

      使用动态规划的办法进行分析,本题可以类比将$n$个小球放入$k$个盒子中,小球、盒子无差别,且盒子不能为空。设置将$n$个小球放入$k$个盒子中方法为$f[n][k]$,因此为了使每个盒子中不为空,先拿出$k$个小球分别分别放到每个盒子中,再将$n-k$个小球放入$k$个盒子中,其方法数为$f[n-k][k]$;或者保证每个盒子至少有一个小球,先将1个小球放入1个盒子中,剩余的$n-1$个小球放入$k-1$个盒子中其方法数为$f[n-1][k-1]$,因此总的方法数为$f[n][k]= f[n-k][k]+ f[n-1][k-1]$。具体步骤如下:
  • 设置$f[n][k]$为整数$n$分成$k$份,且每份不能为空的方法数
  • 其转移方程为$f[n][k]=f[n-k][k]+f[n-1][k-1]$
  • 初始化$f[0][0]=1$,只有当$n$大于等于$k$才可进行划分。
状态转移方程:


图解

输入:5,3
返回值:2

C++核心代码

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int 被划分的数
     * @param k int 化成k份
     * @return int
     */
        int mod=1000000007;
    int divideNumber(int n, int k) {
        // write code here
        if(n<k) return 0;
        vector<vector<int>> f(n+1,vector<int>(k+1));
        f[0][0]=1;//初始化
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=k;j++)
            {
                if(i>=j)//类比小球数量必须大于盒子数量k
                   f[i][j]=(f[i-1][j-1]+f[i-j][j])%mod;
            }
        }
        return f[n][k];
    }
};

复杂度分析

  • 时间复杂度:设置内外两层嵌套循环,外层循环次数为$n$,内层循环次数$k$,因此总的时间复杂度为$O(n*k)$
  • 空间复杂度:借助于一个二维数组$f[n][k]$用于存放整数$n$分成$k$份,且每份不能为空的方法数,因此空间复杂度为$O(n*k)$

方法二

思路分析

     直接采用暴力搜索的办法,对于一个整数,首次分配我们可以分配1,2,...,接着如果选定第一次分配1,那么第二次可以分配1,2,...,一直往下,直到所有的分配数之和为整数n,分配次数为k,此时这是一种分配的方案数。

图解



C++核心代码

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int 被划分的数
     * @param k int 化成k份
     * @return int
     */
    int depth;
    int sum;//存储划分方案数
    int divideNumber(int n, int k) {
        // write code hereif
        depth=k;
        sum=0;
        if(n<k) return 0;
        search(0,n,1);//第一个参数表示搜索的深度层数,第二个参数表示整数n的剩余数,第三个参数表示分配数开始的数,由于不能为空,因此开始为1
        return sum;
        
        
    }
    void search(int dep,int res,int start)
    {
        if(dep==depth+1)
        {
            if(res==0)//整数n已经分配完成,是一种分配方案
                sum++;
            return;
        }
        for(int i=start;i<=res;i++)//继续向下搜索,并且层数+1,n剩余的数需要减去开始的数
        {
            search(dep+1,res-i,i);
        }
    }
    
};

复杂度分析

  • 时间复杂度:将整数$n$分为$k$个,且每个不能为空,可类比为将$n$个小球使用挡板隔开,挡板数为$k-1$,需要的操作为,因此时间复杂度为
  • 空间复杂度:每次递归的空间为$O(1)$,共递归$k$次,因此空间复杂度为$O(k)$