前言
动态规划第8篇。记录 八十一【343. 整数拆分】
一、题目阅读
给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。返回 最大乘积 。
示例 1:
输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
提示:
2 <= n <= 58
二、尝试实现
分析题目,确定方法
- 题目给了一个正整数n,拆分成k个正整数,使得k个正整数乘积最大。但是没有说k能是几。感觉是一种尝试的过程,好像没有方法。
- 思考:a + b = 常数;当a=b时,a * b 最大。这个结论在初高中都学过:当几个数的和固定,那么这几个数相等时乘积最大。所以:当n拆分成近似相等的k个数,乘积才会最大。
- 有了结论后,思考能拆成几个数呢?也就是k如何确定?比如n=11:
- 所以思路不正确。
三、参考学习
【343. 整数拆分】 参考学习链接
3.1动态规划思路
- 原理:分成两个因子,固定一个j,可以继续对n-j拆分。有了固定,就方便遍历。
- 同时可以发现下图中:两个因子在n/2之后重复,颠倒顺序而已。所以,遍历到n/2即可结束。
- dp数组含义和下标含义:一维数组dp[i]。当n=i时,拆分因子后的最大乘积值是dp[i]。
- 递推公式:
- 当拆成两个因子:j * (i-j);
- 当拆成三个及以上的因子:固定j,继续对i-j拆分。那么i-j拆分后的最大乘积是dp[i-j]。所以此时乘积为j * dp[i-j]。
- 选择最大的:max(j * (i-j) , j * dp[i-j])。
- dp[i] = max(j * (i-j) , j * dp[i-j] , dp[i]);为什么有dp[i]?因为j是不断遍历的,dp[i]暂时存放某一个j得出的最大乘积。所以加上dp[i]后比较。
- 初始化:
- dp[0]没有含义,dp[0] = 0作为因子乘以 j 后依然是0。不影响结果。
- dp[1]没有含义,dp[1] = 0作为因子乘以 j 后依然是0。不影响结果。
- dp[2] = 1.
- 遍历顺序:递推公式dp[i]用到dp[i-j]在dp[i]的前面,所以遍历从前往后。
3.2代码实现【动态规划】
class Solution {
public:
int integerBreak(int n) {
//定义dp数组
vector<int> dp(n+1,0);
//初始化
dp[2] = 1;
//遍历顺序
for(int i = 3;i < n+1;i++){//总和为i
for(int j = 1;j <= i/2;j++){//拆分两个因子
dp[i] = max(max(j * (i-j), j * dp[i-j]),dp[i]);
}
}
return dp[n];
}
};
3.3贪心算法思路
- 应用结论:把n拆成多个3,直到无法再拆成3,最后如果是4,则保留4作为最后一个因子。这样得到的乘积最大。
- 2是质数,3是质数,没有别的加数,拆到因子包含多个3这一步,对于3无法再继续拆。那么将n尽可能的拆成多个3,最后保留小于等于4的因子,因子之间近似相等,并且无法拆分。
- 为什么不是到质数5停止?因为5 = 2+3,还有两个加数。(不是严格证明,可以这样辅助理解下)
3.4 代码实现【贪心:尽可能的拆分多个3】
class Solution {
public:
int integerBreak(int n) {
if(n == 2) return 1;
if(n == 3) return 2;
if(n ==4) return 4;
int result = 1;
while(n > 4){
result *= 3;
n -= 3;
}
result *= n;
return result;
}
};
总结
本题需要掌握正确思路,获取方法。贪心给出一种说法可以解释原因,非严格数学证明。
(欢迎指正,转载标明出处)