vlambda博客
学习文章列表

写写代码系列032:整数拆分(动态规划)


题目描述


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

示例1:如n = 2,则返回1(2 = 1 + 1,1 * 1 = 1)

示例2:如n = 10,则返回36(10 = 3 + 3 + 4,3 * 3 * 4 = 36)


写写代码系列032:整数拆分(动态规划)


题目解析


本题是leetcode上343号问题。

解法1:暴力解法

当一个问题没有头绪的时候,我们可以想一想,是否可以用枚举的方法,计算出全部拆分方案,以此来解决问题呢?事实上,对于本问题,回溯遍历将一个数做分割的所有可能性,是可以解决的,时间复杂度是指数级的,为O(2^n)。

我们以分割4为例。第一步,可以将4分割为1+3,2+2,3+1三种方案。

写写代码系列032:整数拆分(动态规划)

在这样的分割策略下,我们只需要求出分割3获得的最大乘积,再乘以1,就是一个备选答案。同理,我们获得分割2获得的最大乘积,再乘以2,又是一个备选答案。我们获得分割1获得的最大乘积,再乘以3,又是一个备选答案。这三个备选答案中取最大值就解决了分割4最大乘积这个问题。接下来,对于分割3,分割2,分割1获得最大乘积又可以按照同样的策略进行分割:将3分割成1+2,2+1,将2分割成1+1,1不可再分。

写写代码系列032:整数拆分(动态规划)

现在,对于4的所有分割形式就都遍历出来了,我们也能比较出所有分割方式中最大的乘积是谁。对于更一般的形式,如果我们是分割n获得最大乘积,递归树则如下所示。

写写代码系列032:整数拆分(动态规划)

事实上,求解分割n所获得的最大乘积这样的一个最优化问题,可以拆分成若干个子问题,通过求子问题的最优解,可以获得原问题的最优解,这样的形式叫做最优子结构。比如,通过求解分割n-1获得的最大乘积,分割n-2获得的最大乘积,……,分割1获得的最大乘积,将这些子问题的答案综合起来,就可以获得分割n获得的最大乘积的答案。事实上,也正是因为最优子结构的存在,我们上述的递归树才得以存在。


解法2:动态规划

基于最优子结构的思路,我们需要有一个数组,这个数组用来储存分割1获得的最大乘积,分割2获得的最大乘积,……,分割n获得的最大乘积这些答案,而我们所需要返回的就是数组中的最后一个元素:分割n获得的最大乘积。事实上,根据最优子结构的思路,分割i获得的最大乘积 = max(j * (i - j),j * 分割i-j的最大乘积),其中1≤j≤i-1,而分割i-j的最大乘积,又可以根据最优子结构的思路继续往前找。


写写代码系列032:整数拆分(动态规划)


程序实现



class Solution:
    def integerBreak(self, n: int) -> int:
        # 初始化储存分割i最大结果的数组,memo[i]表示将数字i分割后得到的最大乘积
        memo = [-1] * (n + 1)
        memo[0] = 1
        for i in range(1,n + 1):
            # 求解memo[i]
            for j in range(1,i):
                # 将i分割成j和i-j两部分
                # 分割i的最大乘积为:j*(i-j),j*将i-j继续分割的最大乘积
                # 根据最优子结构,将i-j继续分割的最大乘积就是memo[i-j]
                memo[i] = max(max(memo[i],j * (i - j)),j * memo[i - j])
        return memo[n]


写写代码系列032:整数拆分(动态规划)


写写代码系列032:整数拆分(动态规划)

扫码关注