剪绳子问题:

题目:

给你一根长度为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。

 

int maxProductAfterCutting(int length)
{
if(length<2)return 0 ;
if(length==2)return 1;
if(length==3)return 2;//这里是2而不是下面的3,是因为长度为3时,分成2段取到最大值2

//products[i]存放的是第长度为i的绳子剪成若干段后,各段乘机的最大值,
int*products=new int[length+1];
products[0]=0;//分成0段,最大值为0,为下面的递归提供初始值
products[1]=1;//分成1段,最大值为1,为下面的递归提供初始值
products[2]=2;//分成2段,最大值为2,为下面的递归提供初始值
products[3]=3;//分成3段,最大值为3,为下面的递归提供初始值

for(int i=4;i<=length;++i)//i是递增的,也就是绳子长度是递增的,先求出长度为i的乘机最大值,
{
//在求product[i]之前,对于每一个j(分割后每一小段绳子的长度)而言,product[j]都已经求出来了,并 //且结果保存在projects[j]里,为了求解product[j],需要求出所有可能的product[j]*product[i-j]并
//比较他们的最大值。
for(int j=1;j<=(i/2);++j)
{
int product=product[j]*product[i-j];
if(max<product)
max=product;
product[i]=max;
}
}
max=products[length];
delete[] products;

return max;

}

更新1:

#include <stdio.h>
class Solution {
public:
int cutRope(int number) {
if (number == 2) {
return 1;
}
else if (number == 3) {
return 2;
}

vector<int> f(number + 1, -1);//赋值初值都是-1
for (int i = 1; i <= 4; ++i) {
f[i] = i;
}
for (int i = 5; i <= number; ++i) {
for (int j = 1; j < i; ++j) {
printf("f[i]=%d,j=%d,f[i-j]=%d,\n",f[i],j,f[i-j]);
f[i] = max(f[i], j * f[i - j]);
}
}
return f[number];
}
};
// 长度为8:,
// 剪成1段;长8;最大乘机8
// 剪成2段;长4 4 最大乘机16
// 剪成3段;长1 1 6 / 1 2 5 /134/
// 143/152/161/
// 215/224/233/
// 242/251/314/

输入18
输出729
结果如下,请自行理解
f[i]=-1,j=1,f[i-j]=4,
f[i]=4,j=2,f[i-j]=3,
f[i]=6,j=3,f[i-j]=2,
f[i]=6,j=4,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=6,
f[i]=6,j=2,f[i-j]=4,
f[i]=8,j=3,f[i-j]=3,
f[i]=9,j=4,f[i-j]=2,
f[i]=9,j=5,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=9,
f[i]=9,j=2,f[i-j]=6,
f[i]=12,j=3,f[i-j]=4,
f[i]=12,j=4,f[i-j]=3,
f[i]=12,j=5,f[i-j]=2,
f[i]=12,j=6,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=12,
f[i]=12,j=2,f[i-j]=9,
f[i]=18,j=3,f[i-j]=6,
f[i]=18,j=4,f[i-j]=4,
f[i]=18,j=5,f[i-j]=3,
f[i]=18,j=6,f[i-j]=2,
f[i]=18,j=7,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=18,
f[i]=18,j=2,f[i-j]=12,
f[i]=24,j=3,f[i-j]=9,
f[i]=27,j=4,f[i-j]=6,
f[i]=27,j=5,f[i-j]=4,
f[i]=27,j=6,f[i-j]=3,
f[i]=27,j=7,f[i-j]=2,
f[i]=27,j=8,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=27,
f[i]=27,j=2,f[i-j]=18,
f[i]=36,j=3,f[i-j]=12,
f[i]=36,j=4,f[i-j]=9,
f[i]=36,j=5,f[i-j]=6,
f[i]=36,j=6,f[i-j]=4,
f[i]=36,j=7,f[i-j]=3,
f[i]=36,j=8,f[i-j]=2,
f[i]=36,j=9,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=36,
f[i]=36,j=2,f[i-j]=27,
f[i]=54,j=3,f[i-j]=18,
f[i]=54,j=4,f[i-j]=12,
f[i]=54,j=5,f[i-j]=9,
f[i]=54,j=6,f[i-j]=6,
f[i]=54,j=7,f[i-j]=4,
f[i]=54,j=8,f[i-j]=3,
f[i]=54,j=9,f[i-j]=2,
f[i]=54,j=10,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=54,
f[i]=54,j=2,f[i-j]=36,
f[i]=72,j=3,f[i-j]=27,
f[i]=81,j=4,f[i-j]=18,
f[i]=81,j=5,f[i-j]=12,
f[i]=81,j=6,f[i-j]=9,
f[i]=81,j=7,f[i-j]=6,
f[i]=81,j=8,f[i-j]=4,
f[i]=81,j=9,f[i-j]=3,
f[i]=81,j=10,f[i-j]=2,
f[i]=81,j=11,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=81,
f[i]=81,j=2,f[i-j]=54,
f[i]=108,j=3,f[i-j]=36,
f[i]=108,j=4,f[i-j]=27,
f[i]=108,j=5,f[i-j]=18,
f[i]=108,j=6,f[i-j]=12,
f[i]=108,j=7,f[i-j]=9,
f[i]=108,j=8,f[i-j]=6,
f[i]=108,j=9,f[i-j]=4,
f[i]=108,j=10,f[i-j]=3,
f[i]=108,j=11,f[i-j]=2,
f[i]=108,j=12,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=108,
f[i]=108,j=2,f[i-j]=81,
f[i]=162,j=3,f[i-j]=54,
f[i]=162,j=4,f[i-j]=36,
f[i]=162,j=5,f[i-j]=27,
f[i]=162,j=6,f[i-j]=18,
f[i]=162,j=7,f[i-j]=12,
f[i]=162,j=8,f[i-j]=9,
f[i]=162,j=9,f[i-j]=6,
f[i]=162,j=10,f[i-j]=4,
f[i]=162,j=11,f[i-j]=3,
f[i]=162,j=12,f[i-j]=2,
f[i]=162,j=13,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=162,
f[i]=162,j=2,f[i-j]=108,
f[i]=216,j=3,f[i-j]=81,
f[i]=243,j=4,f[i-j]=54,
f[i]=243,j=5,f[i-j]=36,
f[i]=243,j=6,f[i-j]=27,
f[i]=243,j=7,f[i-j]=18,
f[i]=243,j=8,f[i-j]=12,
f[i]=243,j=9,f[i-j]=9,
f[i]=243,j=10,f[i-j]=6,
f[i]=243,j=11,f[i-j]=4,
f[i]=243,j=12,f[i-j]=3,
f[i]=243,j=13,f[i-j]=2,
f[i]=243,j=14,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=243,
f[i]=243,j=2,f[i-j]=162,
f[i]=324,j=3,f[i-j]=108,
f[i]=324,j=4,f[i-j]=81,
f[i]=324,j=5,f[i-j]=54,
f[i]=324,j=6,f[i-j]=36,
f[i]=324,j=7,f[i-j]=27,
f[i]=324,j=8,f[i-j]=18,
f[i]=324,j=9,f[i-j]=12,
f[i]=324,j=10,f[i-j]=9,
f[i]=324,j=11,f[i-j]=6,
f[i]=324,j=12,f[i-j]=4,
f[i]=324,j=13,f[i-j]=3,
f[i]=324,j=14,f[i-j]=2,
f[i]=324,j=15,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=324,
f[i]=324,j=2,f[i-j]=243,
f[i]=486,j=3,f[i-j]=162,
f[i]=486,j=4,f[i-j]=108,
f[i]=486,j=5,f[i-j]=81,
f[i]=486,j=6,f[i-j]=54,
f[i]=486,j=7,f[i-j]=36,
f[i]=486,j=8,f[i-j]=27,
f[i]=486,j=9,f[i-j]=18,
f[i]=486,j=10,f[i-j]=12,
f[i]=486,j=11,f[i-j]=9,
f[i]=486,j=12,f[i-j]=6,
f[i]=486,j=13,f[i-j]=4,
f[i]=486,j=14,f[i-j]=3,
f[i]=486,j=15,f[i-j]=2,
f[i]=486,j=16,f[i-j]=1,
f[i]=-1,j=1,f[i-j]=486,
f[i]=486,j=2,f[i-j]=324,
f[i]=648,j=3,f[i-j]=243,
f[i]=729,j=4,f[i-j]=162,
f[i]=729,j=5,f[i-j]=108,
f[i]=729,j=6,f[i-j]=81,
f[i]=729,j=7,f[i-j]=54,
f[i]=729,j=8,f[i-j]=36,
f[i]=729,j=9,f[i-j]=27,
f[i]=729,j=10,f[i-j]=18,
f[i]=729,j=11,f[i-j]=12,
f[i]=729,j=12,f[i-j]=9,
f[i]=729,j=13,f[i-j]=6,
f[i]=729,j=14,f[i-j]=4,
f[i]=729,j=15,f[i-j]=3,
f[i]=729,j=16,f[i-j]=2,
f[i]=729,j=17,f[i-j]=1,

动态规划与贪心算法

动态规划

如果要求一个问题的最优解,而且该问题能分成若干个子问题,整体问题的最优解依赖于子问题的最优解,可以考虑用动态规划解决。

动态规划一般是自顶向下思考,自底向上实现的,为了避免计算子问题时重复,可以将子问题的结果打表记录下来。

贪心法

使用贪心法解决问题,要从数学上证明这个贪心策略一定能取到最优解,这样每一步都做贪心选择,实现起来要容易很多。

面试题14:剪绳子

给你一根长度为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和n-i,在这个上面还可能继续减(子问题),所以:
f ( n ) = m a x ( f ( i ) × f ( n − i ) ) f(n)=max(f(i)\times f(n-i))f(n)=max(f(i)×f(n−i))
然后自下而上的解决问题,可以从f(1)开始向上计算并打表保存,最终获得f(n)的值。

#include<bits/stdc++.h>
using namespace std;

//输入绳子的长度,输出最大乘积
int maxProductAfterCutting_solution1(int length) {
if(length < 2)//因为要求长度n>1,所以这里返回0表示输入非法
return 0;
if(length == 2)//长度为2时,因为要求剪下段数m>1,所以最大是1x1=1
return 1;
if(length == 3)//长度为3时,因为要求剪下段数m>1,所以最大是1x2=2
return 2;

//运行至此,说明绳子的长度是>3的,这之后0/1/2/3这种子问题最大就是其自身长度
//而不再需要考虑剪一刀的问题,因为它们剪一刀没有不剪来的收益高
//而在当下这么长的绳子上剪过才可能生成0/1/2/3这种长度的绳子,它们不需要再减
//所以下面的表中可以看到它们作为子问题的值和上面实际返回的是不一样的

//在表中先打上子绳子的最大乘积
int* products = new int[length + 1];
products[0] = 0;
products[1] = 1;
products[2] = 2;
products[3] = 3;//到3为止都是不剪最好

int max = 0;//用于记录最大乘积
//对于4以上的情况,每次循环要求f(i)
for(int i = 4; i <= length; ++i) {
max = 0;//每次将最大乘积清空
//因为要计算f(j)乘以f(i-j)的最大值,j超过i的一半时是重复计算
//所以只需要考虑j不超过i的一半的情况
for(int j = 1; j <= i / 2; ++j) {
//计算f(j)乘以f(i-j)
int product = products[j] * products[i - j];
//如果比当前找到的还大
if(max < product)
max = product;//就把最大值更新
}
//这里是循环无关的,书上在for里面,我把它提出来
products[i] = max;//最终,更新表中的f(i)=max(f(j)·f(i-j))
}
//循环结束后,所求的f(length)也就求出来了
max = products[length];//将其记录下来以在销毁后能返回
delete[] products;//销毁打表的辅助空间
return max;
}

int main(){
cout<<maxProductAfterCutting_solution1(9)<<endl;
return 0;
}

当n > = 5时,3 ( n − 3 ) > = 2 ( n − 2 ) 且只在n取5时取等号,且它们都大于n nn,所以应把绳子剪成尽量多的3,让剩下的都是2这样的组合。

书上言尽于此,我想了下如果剪成其它大小的会不会更好。如果剪成4,那么4 = 2 × 2 = 2 + 2 就还是和剪成两个2一样,如果剪成5,那么5还能继续剪剪成3和2,往后也是一样。试想更大的数,比如15,得到一段15以后不剪是不可能的,因为前面说了这时候3 ( n − 3 ) > 2 ( n − 2 ) > n ,那么剪成更小的段,只要不小于5就一定满足这个就要继续减,如果比5小,从1~4的情况都想过了,4是不用管的或者剪成两个2,3就保留,2也保留,1不要出现。

#include<bits/stdc++.h>
using namespace std;

//输入绳子的长度,输出最大乘积
int maxProductAfterCutting_solution2(int length) {
if(length < 2)//因为要求长度n>1,所以这里返回0表示输入非法
return 0;
if(length == 2)//长度为2时,因为要求剪下段数m>1,所以最大是1x1=1
return 1;
if(length == 3)//长度为3时,因为要求剪下段数m>1,所以最大是1x2=2
return 2;

//尽可能多地减去长度为3的绳子段,这里是计算一下能减出多少个3
int timesOf3 = length / 3;

//当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子段。
//此时更好的方法是把绳子剪成长度为2的两段,因为2*2>3*1。
if(length - timesOf3 * 3 == 1)//如果最后只剩一个1
timesOf3 -= 1;//就要留下一个3

//保证剩下的一定是4或者2或者0,计算一下能剪出几个2来(只有2/1/0三种情况)
int timesOf2 = (length - timesOf3 * 3) / 2;

//3的多少个3次幂,再乘以2的多少个2次幂,就是贪心选择的最优解
return (int) (pow(3, timesOf3)) * (int) (pow(2, timesOf2));
}

int main(){
cout<<maxProductAfterCutting_solution2(9)<<endl;
return 0;
}
//01背包问题
#include<iostream>
#include<string.h>
using namespace std;
int dp[220][5500];
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
int main()
{
int n=4,m=20;//n:背包数目 m:最大容量
int i,j,w[220],v[220];//w[i]重量(可理解为第i个背包容量) ,v[i]价值
for(i=1;i<=n;i++)//下标从1记录方便理解
{
cin>>w[i]>>v[i];
}
//动态规划dp[i][j] 记录 用j总容量去背起前i个背包中的若干个背包 的最大价值
//将用j总容量背起前i个背包中若干个背包的问题变为:背第i个背包+用剩余容量j-w[i]去背起前i-1个背包中的若干个背包.
//即dp[i][j]=v[i]+dp[i-1][j-w[i]];另外还有一种考虑不背第i个背包的即为:dp[i][j]=dp[i-1][j];
//到此2种情况要判断取最大,故得出状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
//可见求前i个背包用j容量的最大价值需要先知道dp[i-1][j]和dp[i-1][j-w[i]]这2个值已经在计算i的前算出.
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
dp[i][j]=max(dp[i][j],dp[i-1][j]);
if(j-w[i]>=0)
dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
}
}
cout<<dp[n][m]<<endl;
return 0;
}