题目描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。


  首先正确理解题意,一段绳子,可以剪成多段,求最大乘积。里面有两个变量,一个是绳长,一个是减的段数。而给定绳长后,在编程过程中,主要考虑减的段数的变化。


思路1:动态规划。

  整体可由局部和局部的最优解相乘得到,如果把长度n绳子的最大乘积记为f(n),则有:f(n)=max(f(i)*f(n-1)),因此不断由小推导至大,最终求出n的结果。因此设置一个数组用来存放对应的最大乘积数。
  然后本题很关键的一点是,推断的起点设计在4。最大乘积其实是反复出现2和3。规律如下:

  • 当target等于1,2,3的时候,结果是固定的
  • 当target大于3的时候,可以看以下数据
  • target=4, 最优解:2 2
  • target=5, 最优解:3 2
  • target=6, 最优解:3 3
  • target=7, 最优解:3 2 2
  • target=8, 最优解:3 3 2
  • target=9, 最优解:3 3 3
  • target=10,最优解:3 3 2 2
  • target=11,最优解:3 3 3 2
  • target=12,最优解:3 3 3 3

  因此,在使用递推公式时,必须由4开始使用。4前面的都当做特例分析了。刚刚提到设计了一个数组用来存放最大乘积值,这个表述并不是十分准确,准确表述是,product[i]表示,当剪操作后,绳子的某段长度变成i时,i可以减或者不减而形成的最大乘积值是多少。因此product[3]就是不操作而单独拿一个3。而不是3剪操作后形成的最大,如果3剪操作,最大也就1*2=2。所以这里的几个初始值很关键。

注意:很多人都设计了product[0]和product[1],其实没必要,因为我们知道就是由2和3构成的,因此我们后续也只会用到2和3。所以下面的代码将很多题解中的代码进行一点优化。去掉了0和1的赋值,以及j从2开始而不是从1或者从0。

public class Solution {
    public int cutRope(int target) {
        if (target <= 1)//绳长1或者0执行剪操作
            return 0;
        if (target == 2)//绳长2执行剪操作
            return 1;
        if (target == 3)//绳长3执行剪操作
            return 2;
        int[] product = new int[target + 1]; // 用于存放最大乘积值
        //下面几个值设定的很重要。

        product[2] = 2;//剩余绳长为2可形成的最大乘积(可剪可不剪,不减就是直接拿2最大)
        product[3] = 3;//剩余绳长为3可形成的最大乘积(可剪可不剪,不减就是直接拿3最大)

        // 开始从下到上计算长度为i绳子的最大乘积值product[i]
        for (int i = 4; i <= target; i++) {
            int max = 0;
            for (int j = 2; j <= i / 2; j++) {//j到i/2即可,从i/2到i并不会更新最大值
            //且由于我们知道要么取2要么取3,因此我们也直接从j=2开始计算起。
                if (max < product[j] * product[i - j])
                    max = product[j] * product[i - j];
            }
            product[i] = max;
        }
        return product[target];
    }
}

思路2:贪心

  当我们发现了就是2和3构成最终解时,可以利用这两个数做文章。即将一个数分解成2和3.由解法1的规律可看到,我们是尽量能分成3就分成3,不能分成3的时候就用一个2.因此9是3个3而不是4个2.并且注意到,7不是分解成2个3和1个1,这是因为我们只分成2和3,如果剩下4这种模上3余1时,我们就用2个2代替(其实我们2的个数最多为2个最少没有)。根据此规律编写代码。贪心策略就是每次取当前的最佳状况,而本题的最佳状况就是能取3就取3,不能取的时候就用2代替.
  因此我们将给的长度n除以3,余数取决于2的个数,余0全是3,余1则是2个2(3的个数则是商减1),余2则说明就1个2.

public class Solution {
    public int cutRope(int target) {
        if (target <= 1)
            return 0;
        if (target == 2)
            return 1;
        if (target == 3)
            return 2;

        int x = target % 3;//看除以3余多少,来决定2的个数
        int y = target / 3;//看3的个数
        if (x == 0) {//全3
            return (int)Math.pow(3, y);//需要强制转换一下
        } else if (x == 1) {//2个2,剩余都是3,注意y要减1,比如4
            return 2 * 2 * (int)Math.pow(3, y - 1);
        } else {//x==2,只有1个2
            return 2 * (int)Math.pow(3, y);
        }
    }
}