【LeetCode】343. 整数拆分(同剑指 Offer 14- I && 剑指 Offer 14- II )

这篇博客介绍了如何解决LeetCode的整数拆分问题,以最大化拆分后的整数乘积。文章通过动态规划、数学推导和贪心策略三种方法详细解释了解决方案,强调了在拆分过程中数字3的重要性,并提供了不同方法的时间和空间复杂度分析。
摘要由CSDN通过智能技术生成

一、题目

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

示例 1:

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

示例 2:

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

二、解决

1、动态规划

版本1

思路:

n为正整数,if n>=2, then n 可拆成两个整数之和, 即 n = (n-k)+k。 n-k 可继续拆分,也可以不拆。

1、状态定义
dp[i]:表示将 i 拆分后的正整数积的最大值。
 特别: 0不是正整数,1最小正整数,dp[0] = dp[1] = 0。

举例:i>=2,拆分出第一个正整数为 j (1<=j<i)
1)拆分一次,乘积 = j*(i-j)
2)拆分多次,乘积 = j*dp[i-j]
因此:当j固定时,有dp[i] = max(j*(i-j), j*dp[i-j])2、转移方程:
dp[i] = max{ j*(i-j), j*dp[i-1] },   1<=j<i

代码:

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n + 1];
        for (int i = 2; i <= n; i++) {
            int curMax = 0;
            for (int j = 1; j < i; j++) {
                curMax = Math.max(curMax, Math.max(j * (i - j), j * dp[i - j]));
            }
            dp[i] = curMax;
        }
        return dp[n];
    }
}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)

版本2、优化空间

思路:

1、状态定义
dp[i]:表示将 i 拆分后的正整数积的最大值。

2、转移方程:
dp[i] = max{ j*(i-j), j*dp[i-1] },   1<=j<i

想优化,就需要对 {j*(i-j), j*dp[i-1]} 分别考虑。

结论1:根据定义,dp[i] >= j*dp[i-j], 1<=j<i。

结论2:j为奇数,即 j%2==1, j=(j-1/2+(j+1)/2, dp[i] >= (j-1)/2*dp[i-(j-1)/2] >= (j-1)/2*(j+1)/2*dp[i-j].
结论3:j为偶数,即 j%2==0, j=i/2+i/2,因此dp[i]>=i/2*dp[i-i/2]>=j/2*j/2*dp[i-j].

结论4:结合1&&2:
若 j 为奇数,要(j-1)/2*(j+1)/2*dp[i-j] >= j*dp[i-j],则 j>=5.

结论5:结合1&&3:
若 j 为偶数,要j/2*j/2*dp[i-j] >= j*dp[i-j],则 j>=4.

结论6:结合4&&51)若 j>=4, 一定可以将 j 拆分成至少两个正整数的和,且这些分拆数乘积一定大于或等于j。

2)且 j>=4, dp[i]>=j*dp[i-j],可继续推导:
dp[i] >= 2*dp[i-2]>=2*(2*dp[i-4])=4*dp[i-4]

分析1:
j=1无需考虑,因为必有dp[i]>=1*dp[i-1]=dp[i-1]. 即只需要考虑j=2和j=3两种情况。

分析2:
j>=4, dp[i] =?= j*(i-j)?
当 i 固定,要使得 j*(i-j)最大,j的值应该取 j=i/2,要满足 j>=4,则 i>=8,此时i-j>=4。 

结论6-1中,若j>=4, 则dp[j] >= j,所以dp[i-j]>=i-j, 所以 j*dp[i-j] >= j*(i-j)。 

结论7:由分析2可得,j>=4时,dp[i]只需考虑 j*dp[i-j], 不需要考虑 j*(i-j)。

分析3:
结论7中,由于使用j*dp[i-j]计算dp[i]时,j=2和j=3一定由于 j>=4的情况。  why?

结论8:结合分析1&&3:
j>=4, dp[i]=j*dp[i-j]中,j只需要考虑j=2和j=3的情况。

综上所述:
i>=3时,状态转移方程为:dp[i] = max(2*(i-2), 2*dp[i-2], 3*(i-3), 3*dp[i-3])


代码:

class Solution {
    public int integerBreak(int n) {
        if (n < 4) {
            return n - 1;
        }
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            dp[i] = Math.max(Math.max(2 * (i - 2), 2 * dp[i - 2]), Math.max(3 * (i - 3), 3 * dp[i - 3]));
        }
        return dp[n];
    }
}

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

2、数学推导

思路: (主要来自2)

  • 目标

给定整数n,将其拆分为a个数字:
n = n 1 + n 2 + . . . + n a n=n_1+n_2+...+n_a n=n1+n2+...+na
求:
m a x ( n 1 ∗ n 2 ∗ . . . ∗ n a ) max(n_1*n_2*...*n_a) max(n1n2...na)

  • 推导总体步骤
    [1]当拆分成数字相等时,乘积最大。
    [2]最优拆分数字为3.

  • 数学推导

算术几何均值不等式,等号当且仅当 n 1 = n 2 = . . . = n a n_1=n_2=...=n_a n1=n2=...=na成立。
1

推论1:拆分数量a确定,则各拆分数字相等时,乘积最大。

2
推论2:将数字n尽可能以因子3等分时,乘积最大。(根据上图公式推导)

拆分规则:
1、最优:3。 把数字 n 拆分为多个因子3,余数可能为0,1,2三种情况。
2、次优:2。余数为2,八六,不再拆分为1+1。
3、最差:1。若余数为1;则应把一份3+1替换为2+2,因为 22>31。

3

代码 - 版本1:

class Solution {
    public int integerBreak(int n) {
        if(n <= 3) return n - 1;
        int a = n / 3, b = n % 3;
        if(b == 0) return (int)Math.pow(3, a);
        if(b == 1) return (int)Math.pow(3, a - 1) * 4;
        return (int)Math.pow(3, a) * 2;
    }
}

时间复杂度: O ( 1 ) O(1) O(1),幂运算时间复杂度为 O ( l o g a ) O(loga) O(loga),可看做常数。
空间复杂度: O ( 1 ) O(1) O(1)

代码 - 版本2:

下面代码可以解决大整数除余,即14-II。

class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) return n - 1;
        int a = n/3, b = n%3, p = 1000000007; 
        long res = 1, x = 3;
        for (int i = a - 1; i > 0; i /= 2) {
            if ((i & 1) == 1) res = (res*x) % p;
            x = x * x % p;
        }
        if (b == 0) res = res*3%p;
        if (b == 1) res = res*4%p;
        if (b == 2) res = res*6%p;
        return (int)res ;
    }
}

时间复杂度: O ( 1 ) O(1) O(1),幂运算时间复杂度为 O ( l o g a ) O(loga) O(loga),可看做常数。
空间复杂度: O ( 1 ) O(1) O(1)

3、贪心

思路:

推论1:合理切分可带来更大乘积。

推论2:切分方案合理,切分越多,乘积越大。

重要是找到那个临界点,是否又优先级最高长度x存在?若有,则应该尽可能把绳子以x长度切为多段,以获取最大乘积。

推论3:为使乘积最大,只有长度为2和3的绳子不应该再切分,且3比2更优。
4
可以结合上面几种解决方案进行综合理解,上面更严谨一些。

代码:

class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) return n - 1;
        long res=1L;  // 1L其实就是1, L是长整形
        int p=(int)1e9+7;
        // 贪心算法,优先切三,其次切二
        while(n>4){
            res=res*3%p;
            n-=3;
        }
        // 出来循环只有三种情况,分别是n=2、3、4
        return (int)(res*n%p);
    }
}

时间复杂度: O ( 1 ) O(1) O(1),幂运算时间复杂度为 O ( l o g a ) O(loga) O(loga),可看做常数。
空间复杂度: O ( 1 ) O(1) O(1)

三、参考

1、整数拆分
2、343. 整数拆分(数学推导,清晰图解)
3、面试题14- II. 剪绳子 II(数学推导 / 贪心思想 + 快速幂求余,清晰图解)
4、Java DP solution
5、Why factor 2 or 3? The math behind this problem.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值