【经典专题】分割整数得到最大乘积——剪绳子问题

问题引入

给你一根长度为 n 的绳子,请把绳子剪成整数长度的若干段,然后把各段长度相乘,能得到的最大乘积是多少?(注:长度n至少为2,你至少要剪一次)

举个例子:

绳子长度为10,那么我们把它剪成 3+3+4 的多段,可以得到最大值 3×3×4=36 。

 
 

初步结论

2 = 1 + 1,1 × 1 = 12不应该被切
3 = 1 + 2,1 × 2 = 23不应该被切
4 = 2 + 2,2 × 2 = 44切不切都一样
5,6,7,8,9…要切(且任何数字都可以被切分为2和3的组合)

 
 

解法一:动态规划

根据上面的初步结论,不难想到动态规划的写法:

初始化:dp[1]=1(无意义),dp[2]=1,dp[3]=2
状态转移:dp[i] = Math.max(dp[i - 2] * 2, dp[i - 3] * 3)

代码:

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

 
 

解法二:贪心

不妨找找规律:
2=1+1,res=1
3=1+2,res=2
4=2+2,res=4
5=3+2,res=6
6=3+3,res=9
7=3+4,res=12
8=3+3+2,res=18
9=3+3+3,res=27
10=3+3+4,res=36
11=3+3+3+2,res=54

大胆猜测:
在尽可能切分出3的情况下,对4予以保留。
我们可以这样做,用3取余——如果余数为0,那正好全切为3;如果余数为1,那么少切一个3,组成一个4;如果余数为2,那就前面全切为3,最后切一个2。

小心求证:
Krahets大神的提供的数学推导:戳这里

代码:

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

另一种写法:

class Solution {
    public int cuttingRope(int n) {
        if (n == 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        int res = 1;
        while (n > 4) {
            n -= 3;
            res *= 3;
        }
        return res * n;
    }
}

 
 

扩展题目

Leetcode中对原题目进行了修改,将n的取值设置到近150。这样的话,最终结果远超long类型。为此,题目要求最终结果对 1000000007 取余。

这导致了,动态规划法无法使用。因为Math.max运算变得没有意义——比较取模后的数字,并不能反映取模前两个数字的大小。

好在,贪心法还可以使用:

写法一:Math.pow不能用了?那就自己写一个带取模的快速幂!

class Solution {
    public int cuttingRope(int n) {
        if (n == 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        int a = n / 3;
        int b = n % 3;
        if (b == 0) {
            return (int) (quickPow(3, a) % 1000000007);
        } else if (b == 1) {
            return (int) (quickPow(3, a - 1) * 4 % 1000000007);
        } else if (b == 2) {
            return (int) (quickPow(3, a) * 2 % 1000000007);
        }
        return 0;
    }

	// 快速幂(非递归实现,带取模)
    private long quickPow(long a, int n) {
        long res = 1;
        while (n > 0) {
            if ((n & 1) == 1) {
                res *= a;
                res %= 1000000007;
            }
            a *= a;
            a %= 1000000007;
            n >>= 1;
        }
        return res;
    }
}

写法二:似乎更加简单,每步取模即可。

class Solution {
    public int cuttingRope(int n) {
        if (n == 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        long res = 1;
        while (n > 4) {
            n -= 3;
            res *= 3;
            res %= 1000000007;
        }
        return (int) (res * n % 1000000007);
    }
}

 
 

理解本质

最后,再通俗的说一下贪心法的数学推导过程。

思考一下,一个长方形的周长一定,如何得到最大面积?很简单,使其成为一个正方形。

同理,我们把绳子剪成若干个长度相同的段,就能使得所有段的乘积最大

问题是,这个长度取多少合适呢?

列方程求导即可。答案是长度为3(驻点实际上是e)。

所以,我们尽量把绳子剪成若干个长度为3的段,如果不凑整是3的倍数,那么就退而求其次,最后剩下2或4。但是,绝不能出现1。这也是如果余1,也要凑4的原因。

 
 
 
 
 
 
 
 
 
 
 
 

E N D END END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值