【LeetCode动态规划#03】整数拆分(数学题)

发布时间 2023-03-25 14:18:02作者: dayceng

整数拆分

力扣题目链接(opens new window)

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

  • 输入: 2
  • 输出: 1
  • 解释: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

  • 输入: 10
  • 输出: 36
  • 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
  • 说明: 你可以假设 n 不小于 2 且不大于 58。

思路

题目的要求很明确了,就是把整数拆解,让拆分结果相乘,积尽可能大

一个数学结论是:一个整数,如果要拆分之后再相乘得到尽可能大的值,那么拆分时就要拆成尽可能相同的m个数

上述结论在示例2中可以体现

对 10 进行拆分有很多种可能,但乘积相对大的几种可能都集中在拆分之后的数尽可能相同的几种情况中,例如:

5*5
3*3*4

取其中最大的情况作为结果即可(3 * 3 * 4)

再来看一下拆分的过程

还是以 10 为例:

10--->2  8
         _--->2  6
                 _--->2  4
                 ...

10 可以拆成 2 和 8;

然后 8 又进一步拆成 2 和 6;

以此类推,我们发现这里出现了一种递推的关系:

10 依靠 2 和 8 的状态;

8 依靠 2 和 6 的状态;...

因此可以使用dp

五步走

1、确定dp数组含义

给我们的数是 i ,要对 i 进行拆分,所以

dp[i]: 对i进行拆分得到的最大乘积为dp[i]

2、确定递推公式

怎么拆?有两种渠道得到dp[i]:

  • 一个是j * (i - j) 直接相乘
  • 一个是j * dp[i - j],相当于是拆分(i - j)

j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。

假设现在需要拆分的数是 i ,并且需要拆成两个数

那就可以表示如下:

j*(i-j)//j和(i-j)分别是拆分后得到的数

j表示遍历从[1,i]得到的所有情况

如果要拆成两个数或者以上呢?那就要表示如下:

j*dp[i-j]//j和(i-j)分别还是拆分后得到的数

不同的是,我们要对 i-j 进行进一步拆分,得到三个或三个以上的数

需要注意的是:上述拆分方式中,j 是固定的,在拆分 i-j 的过程中就会包括拆分j的所有情况,因此不用再对 j 进行拆分

(联想一下背九九乘法表的场景,横着背和竖着背最后都能得到所有情况)

如果要写成dp[j]*dp[i-j]就有点问题了

​ 第一,这么写表示 i 一定要继续拆分,这样可能就错过最大乘积了,相当于默认将一个数强制拆成4份以及4份以上了;(例如本来3 * 3 * 4最大了,拆i就拆成1 * 2 * 3 * 4,就不一定是最大了)

​ 第二,上述写法在初始化时不符合dp数组的含义;

总结一下

j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * jdp[i - j] * j 取最大的。

递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

(为什么还要加dp[i],后面在代码里解释)

3、确定初始化方法

首先,dp[0]和dp[1]其实是没有意义的,0、1拆不了

然后dp[2]是有意义的,为1,所以:

dp[0] = 0//0*0不影响
dp[1] = 0
dp[2] = 1

4、确定遍历顺序

先看递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。

所以遍历方式如下:

for(int i = 3; i <= n; ++i){
    for(int j = 1; j < i - i; ++j){//从0开始没有意义
        dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)));      
    }
}

递推公式中取最大值时加入dp[i]的原因是,在第二层循环中,我们也要找到最大的dp[i],因为题意就是获取乘积最大的拆分结果(也就是dp[i])嘛

代码

本题代码很简单,就是分析过程比较难(纯数学题)

class Solution {
public:
    int integerBreak(int n) {
        //定义dp数组
        vector<int> dp(n + 1);
        //初始化dp数组
        dp[0] = 0;
        dp[1] = 0;
        dp[2] = 1;
        //遍历
        for(int i = 3; i <= n; ++i){
            for(int j = 1; j < i; ++j){//注意这里的循环条件,遍历范围是i
                dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];//注意返回值是dp[n],不用减1
    }
};

需要注意循环的条件和返回值