题意:
- 将整数n划分成k份,每份不能为空,求划分的种数。
方法一:回溯法
解题思路:
- 分析数划分的规律,n=7,k=3,如图:
- 根据上图我们总结一下此种划分的规律:
(1)右边的数大于等于左边的数
(2)每一个位置都是从最小可能的数开始增加到最大可能的数。
因此按照这种规律可以保证划分的唯一性和完备性。 - 使用深度优先遍历和回溯法的思想来解决本题:用数组nums存储划分的值,辅助函数helper是实现深度优先和回溯,按照上述的规律实现,增加回溯时的剪枝条件left>=nums[cur-1]和i<=left/(k-cur)以减少搜索深度,去掉不必要的搜索。
代码如下:
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int 被划分的数 * @param k int 化成k份 * @return int */ int ans=0; int divideNumber(int n, int k) { // write code here //nums存储划分的值 vector<int> nums(k); int cur=0,left=n; helper(nums,cur,left,k); return ans; } //实现回溯法的辅助函数 void helper(vector<int>&nums,int cur,int left,int k){ //满足方案条件 if(cur==k-1&&left>=nums[cur-1]){ ans++; return; } //剪枝一 if(left<nums[cur-1]) return; //边界条件,当cur==0时,初始化i为1 int i=cur>0?nums[cur-1]:1; //剪枝二:i<=left/(k-cur) for(;i<=left/(k-cur);i++){ //回溯法主体 nums[cur]=i; helper(nums,cur+1,left-i,k); nums[cur]=i; } } };
复杂度分析:
时间复杂度: , 是对划分结果的组合遍历时间复杂度,O(k)是求每个划分结果时的时间复杂度,因此每个划分都是k个数组成。
空间复杂度:,额外数组空间nums,O(k),递归栈空间O(n)。
方法二:动态规划
解题思路:
- 分配一个(n+1)*(k+1)的数组dp,默认值为0,dp[i][j]表示将数i划分为j份的可能数。有如下递推关系:
dp[i][j]=dp[i-1][j-1]+dp[i-j][j]
将dp[i][j]分成两种可能,(1)j个数中存在至少一个1,则这部分数为从将数i-1划分为j-1份的可能数dp[i-1][j-1] (2)j个数中全部大于1,则将所有数减一,那么这部分数为将数i-j划分为j份的可能数dp[i-j][j],当然会有i-j<j的情况,默认值为0。
代码如下:
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param n int 被划分的数 * @param k int 化成k份 * @return int */ int divideNumber(int n, int k) { // write code here //二维数组dp (n+1)*(k+1) vector<vector<int>> dp(n+1,vector<int>(k+1)); int mod=1000000007; cout<<mod<<endl; dp[0][0]=1; for(int i=1;i<=n;i++){ for(int j=1;j<=k;j++){ //递推更新dp[i][j],代表将i分成j份的划分数 if(i>=j) dp[i][j]=(dp[i-1][j-1]+dp[i-j][j])%mod; } } //返回结果 return dp[n][k]; } };
复杂度分析:
时间复杂度: ,双重遍历,外层O(n),内层O(k)。
空间复杂度: ,额外内存空间二维数组dp。