剪绳子
题目
给你一根长度为n的绳子,请把绳子剪成m段(m,n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]×k[1]×…×k[m]的可能最大乘积是多少?例如,当绳子的长度为8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大乘积为18.
动态规划
我们可以进行这样的分析,假设把长度为n的绳子剪成若干段后得到的乘积最大值为函数f(n)。假设我们把第一刀剪在长度为i(0<i<n)的位置上,于是吧绳子剪成了i和n-i上。我们要想得到最优解f(n),就要用同样的方法得到f(i)和f(n-i),也就是说整体问题的最优解是依赖各个问题的最优解——这是动态规划的重要特性。
首先,定义函数f(n)为把长度为n的绳子剪成若干段后各段长度乘积的最大值.在剪第一刀时,有n-1种可能,第一段长度可能为1,2,…,n-1。因此f(n)=max(f(i)xf(n-1)),其中0<i<n
当长度为2时,只能剪成长度为1的两段,因此f(2)=1。当长度为3时,可能是1和2两段,也可能是三段1。又1x2>1x1x1,因此f(3)=2
有了以上分析,就可以写代码了
int maxProductAfterCutting_solution(int length)
{
//length 为1,2,3的情况
if(length<2) return 0;
if(length==2) return 1;
if(length==3) return 2;
//products用来存贮子问题的最优解,第i个元素表示f(i)
std::unique_ptr<int[]> products(new int [length +1]); //使用智能指针(包含在<memory>中),可以不用手动释放内存
products[0]=0;
products[1]=1;
products[2]=2;
products[3]=3;
int max=0;
for(int i=4;i<=length;++i)
{
max=0;
for(int j=1;j<i/2;++j)
{
int product=products[j]*products[i-j];
if(max<product) //求得最大 进行替换
max=product;
products[i]=max;
}
}
max=products[length];
return max;
}
贪心算法
如果我们采用如下的策略来剪绳子,则可以得到最优解:
当n>=5时,尽可能多的剪长度为3的绳子;
当剩下的长度为4时,把绳子剪成两段长为2的绳子
由这样的思路可以写出一下代码:
int maxProductAfterCutting_solution(int length)
{
if(length<2) return 0;
if(length==2) return 1;
if(length==3) return 2;
//尽可能多的剪出长度为3的子段
int timesOf3 = length/3;
//当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子段
//此时更好的方法是把绳子剪成长度为2的两段,因为2x2>3x1
if(length-timesOf3*3==1)
timesOf3-=1;
int timesOf2 = (length-timesOf3*3)/2;
return (int)(pow(3,timesOf3))*(int)(pow(2,timesOf2))
}
使用贪婪算法我们需要证明其具有贪婪性,这样才可以用贪婪法进行求解。
现给出以下证明:
首先,当n>=5时,可以证明2(n-2)>n,并且3(n-3)>3。也就是说,当绳子剩下的长度大于或者是等于5时,我们就把它剪成长度为3或者是2的绳子段,另外当n>=5时,3(n-3)>=2(n-2),因此我们应该尽可能地多剪长度为3的绳子段
其次,要是长度为4,剪一刀有两种结果:1和3或者是2和2。由于2x2>1x3并且2x2=4。当绳子为4时其实没必要剪,只是要求至少一刀而已
————————————————————————————————————————————————————————————————
参考书籍《剑指offer》