整数划分的三种解法

整数划分的三种解法

题目描述:

对于一个整数n 来说可以表示为n = n1 + n2 +
n3 + … + nk 其中n1 >= n2 >= n2 … >= 1
求解n 有多少种划分方法

1、递归

如果n 是4, 可以分为:

4 = 4
4 = 3 + 1
4 = 2 + 2
4 = 2 + 1 + 1
4 = 1 + 1 + 1 + 1

总共5中划分方法,观察上面的等式,可以发现每次都有一个最大的ni 比如4 = 3 + 1, 最大的ni = 3
所以可以定义一个函数f(n, m) 表示最大的ni 不超过m 的划分方法个数
那么最后的答案就是f(n, n)
不是一般性考虑对于任意的f(n, m) 如何转化

1· 如果m > n 由于ni 不会大于n 所以 f(n, m) = f(n, n)
2· 如果n == m, 当ni = n 的时候划分集合只有{n} 这时候可以将ni 单独拿出,f(n, n) = 1 + f(n, n - 1)
3· 当m < n 时, 若ni = m,可以划分为{m, {n1, n2 … nk}} 这么一种集合,sum{n1, n2 … nk} = n - m这时候f(n, m) = (n - m, m)
若ni != m,可以划分为{n1, n2, … nk} 集合,这时候f(n, m) = f(n, m - 1)
4· 当n == 1 || m == 1 只有一种方案 {1, 1, 1,…,1}

所以代码为:

int f(int n, int m) {
	if(n <= 0 || m <= 0) return 0;
	if(n == 1 || m == 1) return 1;
	if(m > n) return f(n, n);
	if(m == n) return f(n, m - 1) + 1;
	return f(n, m - 1) + f(n - m, m);
}

2、完全背包

观察划分的方式可以发现,这是一种特殊的组合问题,从1 ~ n 中选任意的数使得总和等于n 的方案个数

这样就抽像为经典的完全背包问题

物品为1 ~ n 中间的数,体积为i, 价值为i

定义f(i, j) 表示考虑[1, i] 之间的数,使得总和为j 的方案个数

状态转移:f(i, j) = f(i - 1, j) + f(i - 1, j - i) + f(i - 1, j - 2 * i) + … + f(i - 1, j - k * i)

最后答案就是f(n, n), 初始化f(0, 0) = 1

int get_back(int n) {
	int[][] f = new int[n + 1][n + 1];
	f[0][0] = 1; // 初始化
	for(int i = 1; i <= n; ++i) 
		for(int j = 1;j <= n; ++j ) 
			for(int k = 0;k * i <= j; ++k)
				f[i][j] += f[i - 1][j - k * i];
	return f[n][n];
}

时间复杂度:O(n3) ,可以优化为O(n2)这里就不介绍了

3、dp

除了用背包的思想还可以将划分的集合分类,用dp

  1. 划分集合的最小值是1
  2. 划分集合最小值不是1

可以定义dp[i][j] 表示 考虑正好有i 个数使得和为j 的方案数
那么如果最小值为1,可以去掉最后一个1,dp[i][j] = dp[i - 1][j - 1]

如果最小值不是1 可以将集合所有元素都减去1, 总共可以减去i 个1 所以dp[i][j] = dp[i][j - i]

状态转移:dp[i][j] = dp[i - 1][j - 1] + dp[i][j - i]

因此代码为:

int get_dp(int n) {
	int[][] dp = new int[n + 1][n + 1];
	// 初始化 dp[1][i] 集合只有一种可能 {i}
	for(int i = 1;i <= n; ++i ) dp[1][i] = 1;
	
	for(int i = 2;i <= n; ++i )
		for(int j = i;j <= n; ++j)
			dp[i][j] = dp[i - 1][j - 1]  + dp[i - 1][j - i];
	// 最后统计所有的dp[i][n] 就可以
	int res = 0;
	for(int i = 1;i <= n; ++i) res += dp[i][n];
	return res;
	
}

时间复杂度:O(n2)

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
整数划分是一个经典的数学问题,可以用递归的方式进行求解。具体思路如下: 1. 首先确定递归的边界条件,即当划分整数为1时,只有一种划分方式。 2. 对于大于1的整数,可以分成两种情况:一种是包含1的划分方式,一种是不包含1的划分方式。 3. 对于包含1的划分方式,可以递归求解剩余的部分。 4. 对于不包含1的划分方式,可以递归求解剩余部分,但是需要限制划分整数不能大于原来的整数。 下面是一个简单的整数划分的C语言代码实现: ```c #include <stdio.h> int integerPartition(int n, int m) { if (n == 1 || m == 1) { return 1; } else if (n < m) { return integerPartition(n, n); } else if (n == m) { return 1 + integerPartition(n, m - 1); } else { return integerPartition(n, m - 1) + integerPartition(n - m, m); } } int main() { int n; printf("请输入一个正整数n:"); scanf("%d", &n); printf("整数划分的个数为:%d\n", integerPartition(n, n)); return 0; } ``` 在上面的代码中,`integerPartition`函数接受两个参数n和m,表示将n划分成不大于m的正整数划分方式数。函数根据递归边界条件和上述思路进行递归求解,并返回划分方式数。主函数中从输入中读取n,调用`integerPartition`函数计算划分方式数,并输出结果。 需要注意的是,整数划分问题的解法存在重复计算的情况,可以采用动态规划的方式优化求解效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值