动态规划-剪绳子问题

题目

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

题目解析

首先题目描述并不是很清楚,我看了一下,比较准确的描述是,绳子分成m个部分,也就是m段。
默认的条件是

  • 绳子不能不被分割,也就是至少被分为两个部分
  • m、n都是整数,n>1并且m>1(也就是说长度至少为2,而且分割为至少两段)
  • 不同部分绳子的长度都为整数,长度记录为k[0],k[1],…,k[m-1]

目的

  • 在不同切分方法中,找到最大乘积 k[0]×k[1]×…×k[m-1]。

方法解析

本题可以用贪婪算法和动态规划算法求解,虽然贪婪算法的时间复杂度和空间复杂度都比动态规划算法要小,但是要求有一定数学基础,需要定制合理的贪婪策略(面试的时候如果换一道题一般情况下想不出来的)。
一般来说贪婪算法在本题中没有参考价值,最好还是用动态规划的方法来求解。

动态规划

动态规划求解问题的四个特点:

  1. 求一个问题的最优解.
  2. 整体问题的最优解依赖于各个子问题的最优解.
  3. 我们把大问题分解成若干个子问题,这些小问题之间还有相互重叠的更小的子问题.
  4. 用从上往下的顺序先计算小问题的最优解,并存储下来,再以此为基础求取大问题的最优解.

通常按照4个步骤设计一个动态规划算法:

  1. 刻画一个最优解的结构特征(也就是这个最优解怎么描述,有哪些决定的属性呢?)
  2. 递归地定义最优解的值(大问题的最优解是依赖于小问题的最优解的,整体问题的最优解依赖于各个子问题的最优解.).
  3. 计算最优解的值,通常采用自底向上的方法.
  4. 利用计算出的信息构造出一个最优解.

动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保留下来.

动态规划

假设f(n)为长度为n的绳子剑成若干段后的最大乘积。
那么如果我们只剪一刀,该绳子就会分为两部分,假设在i上剪断,我们再求出这两部分绳子的最大乘积f(i),f(n-i)。(f中的参数代表长度,在不同的i上进行切分的话,会有多种方案)
然后不断以相同的方式进行分割,可以得到f(n)=max(f(i)*f(n-i))。 //也就是一次剪掉一个

这是从上至下的递归公式,我们通常采用的是自下而上的解法.

  • 当绳子长度为2时,只可能剪成长度为1的两段,所以f(2)=1
  • 当绳子的长度为3时,可能把绳子剪成长度为1,1,1或1,2,因为12>11*1,所以f(3)=2;

用动态规划自下而上的计算,先算出n为1、2、3的最大乘积,知道小的以后在去算更大的乘积。比如n为4时候最大的可能只能在1*3
这道题目的临界点其实是4,也就是说4以下的其实不论怎么分,成绩都不可能比自身的数大!那么此时存储的其实是最大的值,不一定是乘法最大值。
所以在下面的代码中,fun[1]=1;fun[2]=2;fun[3]=3;其中存放的其实是不切的最优解,这几个数字是比较特殊的。

class Solution {
public:
    int cutRope(int number) {
		//因为至少要分为两段(m>1),所以先排除几个f(n)
        int len=number;
	    if(len<2)
	    	return 0;
	    if(len==2)
	    	return 1;
	    if(len==3)
	    	return 2;
	   // int max=0;//存储当前最大的fun(n)
	    int *fun = new int[len+1];
        memset(fun,0,sizeof(*fun)*(len+1));
	    fun[0]=0;
	    fun[1]=1;
	    fun[2]=2;
	    fun[3]=3; //f[3] 如果二分结果并不是3 而是2最好的分法是不分, f[3] = 3
	    for(int i=4;i<=len;i++) //绳子的长度是从小到大变化的
		{
	    	for(int j=1;j<=i/2;j++) //绳子只需要剪前一半的就可以,剪i和剪n-i一样的			{
            {
                fun[i] = max(fun[j]*fun[i-j],fun[i]);//从某个长度的多种分割方案中找到乘积最大的

	    	}
	    }
	    return fun[len];
    }
};

贪心法

当n<=3时,不再进行剪切,因为会比n小。
当n==4时,将n=2*2,因为只有两种剪法,剪成1,3比较小。
当n>=5时,我们要把所有的绳子都剪成2或者3,同时我们要尽可能的多剪长度为3的绳子,因为3(n-3)>=2(n-2),当剩余的小于5时就没有必要再剪。
简要来说,尽可能分3,不足的就分2。当剩下的绳子长度为4时,把绳子剪成长度为2的绳子。

class Solution {
public:
    int cutRope(int number) {
         int max=1;
         if(number<=3 && number>0){
            return number-1;
        }
        while(number>4) //减到剩4就可以了
		{
            number-=3;
            max*=3;
        }
        return max*number;
    }
};

参考资料

剪绳子——剑指offer——动态规划
算法之动态规划(剑指offer-剪绳子详解)
剑指offer:剪绳子

你可能感兴趣的:(算法题)