题目链接:343. 整数拆分 - 力扣(LeetCode)
前情提要:
因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。
dp五部曲。
1.确定dp数组和i下标的含义。
2.确定递推公式。
3.dp初始化。
4.确定dp的遍历顺序。
5.如果没有ac打印dp数组 利于debug。
每一个dp题目如果都用这五步分析清楚,那么这道题就能解出来了。
题目思路:
本题要求把n拆分为k个正整数和并使这些乘积最大化。
那么我们应该将n拆成几个数呢?拆成俩个?三个?四个?
不好确认是吧。
我们先按照递归五部曲来解析题目。
1.确定dp数组和i下标的含义。
dp[i]就是指将i拆分后获得的最大乘积。
2.确定递推公式。
在确定递推公式之前,我们应该先模拟几遍,找找规律。
比如将10拆分为 2 和 8,8 其实也可以拆分为 2 和 6,6又可以拆分为 2 和 4等等。
其实后面拆分的其实都是8对吧。
那我们可以从中获得以下什么规律呢?
可以简单将i拆分为俩个部分或者多个部分。
拆分为俩个部分的情况,我们需要一个j对i进行遍历,即俩部分拆分为j * (i - j)。
举个例子就是上面的 10 依次可以拆分为 1 9、2 8、3 7等等。
拆分为多个部分的情况,就是j * dp[i - j],其实就是将i - j再进行拆分,得到他i - j的最大乘积。即拆分为多个部分的情况,可以将i - j拆分为俩个也可能是三个等等。所得到的就是将i拆分成了多个部分。
我们是要得到拆分后的最大值,那么每次拆分时,我们都应该将这俩种情况取一个最大值。Math.max(j * (i - j),j * dp[i - j])。
然后我们是要得到dp[i] 的最大值 所以我们的递推公式就是dp[i] = Math.maxa(dp[i],Math.max((j * (i - j),j * dp[i - j]))。
这里我们还需要将每次拆分的俩种情况的最大值跟前几次拆分的最大值进行比较,这样最后得到的就是拆分所有情况后所得到的最大乘积。
3.dp初始化。
这道题的dp初始化很巧妙。
dp[0]和dp[1]都没有意义,因为dp[i] 是让i拆分为k个正整数,1 和 0 都不能再拆,所以我们直接初始化dp[2]就好。
4.确定dp的遍历顺序。
我们先看看dp数组,dp[i] = Math.maxa(dp[i],Math.max((j * (i - j),j * dp[i - j]))。
dp[i]是需要dp[i - j]的状态,即需要他前面的状态,所以遍历顺序一定是从前向后遍历,现有dp[i - j]再有dp[i]。
5.如果没有ac打印dp数组 利于debug。
最后dp[i]模拟后情况就是这样,大家可以打印dp数组对着看看哪与你的不符。
具体还有一些细节我们就在代码中讲。
最终代码:
class Solution {
public int integerBreak(int n) {
int dp[] = new int [n + 1];
dp[2] = 1;
//该题主要就是递推公式
//为什么这里i从3开始遍历
//因为前面 1 和 0都没有意义 2已经初始化了 所以从3开始遍历
for(int i = 3;i <= n;i ++){
//里面就是拆分的逻辑
for(int j = 1;j <= i / 2;j ++){
//为什么这里 j 不从0开始呢? 如果j = 0的话 那0 乘以任何数都为 0 就没有意义 所以j 从 1开始
//为什么这里j <= i/2 而不是 i 呢,其实这里是一个优化。
//当j = i时其实跟j = 0时没有区别 所以j不能等于 i
//为什么是 i / 2 呢,其实当一个数拆分为多个近似相等的数时,才能得到他的最大乘积,这是一个数学定理。
//比如10 是拆分 3 3 4。
//9 拆分为 3 3 3.并且你可以自己模拟,当10拆分为 6 和 4 的时候 一定比 5 5要小。
//所以我们拆分i时,就拆分到i / 2即可,后面比i / 2大的肯定拆分的乘积都比前面小。
dp[i] = Math.max(dp[i],Math.max(j * (i - j),j * dp[i - j]));
}
}
return dp[n];
}
}
这一篇博客就到这了,如果你有什么疑问和想法可以打在评论区,或者私信我。
我很乐意为你解答。那么我们下篇再见!