前言
动态规划专题,从简到难通关动态规划。
每日一题
今天的题目是 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 ,你要去求他拆分过后数字的最大乘积,在不考虑性能的情况下,肯定就是一个个去拆分,两个数,三个数
但是这样会发现,你没有办法规定好拆分多少个数,当 n 越大,你这个拆分的可能性也就越多。
那么我们先假设 f(n) 为 n 这个数拆分过后能够得到的最大乘积,那么 f(n-1) 就为 n-1 拆分过后能够得到的最大乘积,那么我们要怎么通过 f(n-1) 去得到 f(n) 呢,假设从 1 开始将 n 进行拆分,新建一个遍历变量 j,那么在每一轮遍历当中,得到 f(n) 的方式有两种,一种是只拆分两个数 那也就是 j * n-j 在或者就是继续进行拆分,这个时候我们就可以联想到 f(n-1) 的含义: n-1 拆分过后能够得到的最大乘积。
那么就能够得到 f(n) = max(j*(n-j), j*f(n-j))
利用动态规划的五部曲来解决接下来的问题,
-
确定dp数组。根据上面的分析,dp数组应该保存每一个n的最大乘积,也就是dp[i] 代表拆分 i 能够得到的最大乘积。
-
确定递推公式,在分析当中已经确定了前后两个n之间的关系,这也就是dp数组中前后两位的关系,并且因为我们是从 j = 1 开始遍历,不断地去更新 dp[i] 所以每次都要对比前后两个拆分的最大值。
dp[i] = max(dp[i], max(j * (i-j), j * dp[i-j])) -
dp数组初始化,首先明确,拆分 1 是没有意义的,最少要从2开始拆分,但是 1 拆分与否得到的都是 1,所以初始化的时候当做 1 进行初始化就好,所以初始化可以初始化 dp[1] = dp[2] = 1
-
确定遍历顺序,因为我们已经初始化了 1,2 两种情况,所以自然 i 需要从 3 开始遍历,并且一定得是从前往后,3 需要 2 的值,4 需要 3 的值。
-
拿 n=10 作为例子画一下dp数组的值。
根据动态规划得到代码:
function integerBreak(n: number): number {
let dp = new Array(n+1).fill(1)
for(let i=3;i<=n;i++){
for(let j=1;j<i-1;j++){
dp[i] = Math.max(dp[i], Math.max(j * (i-j), j * dp[i-j]))
// dp[i] =
// j * (i-j)
// j * dp[i-j]
}
}
return dp[n]
};