整数拆分问题(动态规划+递归&记录数组)

这篇博客探讨了如何使用动态规划方法解决求解正整数n拆分成最大数为k的不重复拆分方案个数的问题。介绍了边界条件、状态转移方程,并提供了两种实现方式:数组标记法和递归备忘录法。博客内容包括详细的代码示例,分别展示了自下而上的迭代和自上而下的递归解决方案。
摘要由CSDN通过智能技术生成

问题描述:求将正整数 n 无序拆分成最大数为 k 的拆分方案个数,要求所有的拆分方案不重复。

例如 n=5, k=5时, 对应的拆分方案如下:
5 = 5
5 = 4 +1
5 = 3 + 2
5 = 3 + 1 + 1
5 = 2 + 2 + 1
5 = 2 + 1 + 1 + 1
5 = 1 + 1 + 1 + 1 + 1

dp求解问题的特点:最优性原理,无后效性,重叠子问题。

dp求解的核心就是得到状态转移方程,并注意对边界情况的处理。

定义f(n, k)为正整数n拆分成最大数为k的拆分方案个数,下面根据 n, k 不同取值情况写出对应的状态转移方程。

(1)当处于边界情况 n=1 或 k=1 时,明显 f(n, k)=1

(2)n<k 时,f(n, k)=(n, n)

(3)n=k 时,n要么拆分为最大数n的一个方案,或者最大数为n-1的方案,对应f(n, k)=f(n, k-1)+1

(4)n>k 时,拆分方案中分为含 k 和不含 k 两种情况:

  • 如果包含k,那么剩下数之和为n-k,n-k仍有可能大于k,所以对应的状态方程 f(n-k, k)。
  • 如果不包含,那么最大的拆分数变为k-1,对应 f(n, k-1)。

综合以上,当 n>k 时,状态转移方程 f(n, k)=f(n-k, k)+f(n, k-1)。

1. 数组标记法动态规划(自下而上)

每次计算中都是用到之前的记录,这样就可以先从小到大计算出程序,使得计算较大数的时候调用已经计算出的较小的记录,程序直接是用循环就可以完成任务,避免了重复计算和空间栈的开销。

public class Solution {
	public static int dp(int n, int k) {
		int[][] dp = new int[n + 1][k + 1];	
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= k; j++) {
				if (i == 1 || j == 1) {
					dp[i][j] = 1;
				} else if (i < j) {
					dp[i][j] = dp[i][i];
				} else if (i == j) {
					dp[i][j] = dp[i][j - 1] + 1;
				} else {
					dp[i][j] = dp[i - j][j] + dp[i][j - 1];
				}
			}
		}	
		return dp[n][k];
	}

	public static void main(String[] args) {
		System.out.println(dp(5, 5));						// 输出7
	}
}

2.递归+记录数组(自上而下)

该问题本身属于递归的也可采用递归实现,但递归中存在很多相同的子问题,为避免重复计算可增加一个记录数组dp,用dp[n][k]记录f(n, k)的值,初始dp数组全为0,这样如果某个位置不为0便可以直接返回避免计算。这种通过记录数组避免重复计算的方法叫做备忘录方法。

public class Solution {
	public static int dp(int n, int k) {
		int[][] dp = new int[n + 1][k + 1];
		recursion(n, k, dp);
		return dp[n][k];
	}

	private static int recursion(int n, int k, int[][] dp) {
		if (dp[n][k] != 0) {
			return dp[n][k];
		}

		if (n == 1 || k == 1) {
			dp[n][k] = 1;
		} else if (n < k) {
			dp[n][k] = recursion(n, n, dp);
		} else if (n == k) {
			dp[n][k] = recursion(n, k - 1, dp) + 1;
		} else {
			dp[n][k] = recursion(n - k, k, dp) + recursion(n, k - 1, dp);
		}
		return dp[n][k];
	}

	public static void main(String[] args) {
		System.out.println(dp(5, 5));
	}
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值