题目描述
给你一根长度为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。
输入描述:
输入一个数n,意义见题面。(2 <= n <= 60)
输出描述:
输出答案。
示例
输入:
8
输出:
18
本题的解法为动态规划和贪心算法。
贪心算法需要有一定的数学证明能力,针对该题,当绳子长度大于5时,贪心的过程是我们尽可能地剪出长度为3的绳子。下面分别介绍两种方法:
贪心算法
- 贪心算法在对问题求解时,不从整体最优上加以考虑,他所做出的是在某种意义上的
局部最优解
; - 选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关;
- 本题贪心策略:当n>=5时,尽可能多地剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子。
//方法1:贪心,考虑局部最优
//贪心算法,效率高,需要用数学公式进行推导,可以从长度为2的绳子开始推,要尽可能的剪出长度为3的绳子
//时间复杂度为线性O(1)
//时间和内存消耗:13ms 9784k
public int cutRope(int target) {
//鲁棒性判断
if(target < 1){
return 0;
}
//因为必须要剪一刀,所以剪后乘积为1
if(target == 2)
return 1;
if(target == 3)
return 2;
//当target>=4时讨论,尽可能多地剪出长度为3的绳子【关键】
int timesof3 = target / 3;
int timesof2 = 0;
//当最后要剪的一段为4时,不能再剪3了,因为2*2>3*1
if(target - timesof3 * 3 == 1){
timesof3--;
}
timesof2 = (target - timesof3 * 3)/2;
int area = 0;
area = (int) Math.pow(3,timesof3) * (int) Math.pow(2,timesof2);
return area;
}
分析:1、当target<5时,若target不等于4,则无论怎么剪,乘积都小于target;若target等于4,在剪了两段2的情况下,2*2=4;
2.、当target>=5时,要尽可能多地剪长度为3的绳子段。
动态规划
动态规划思想:
- 求一个问题的最优解;
- 整体的问题的最优解是依赖各个
子问题
的最优解; - 把大问题分解成若干个小问题,这些小问题之间还有互相重叠的更小的子问题;
- 为避免子问题的重复计算,我们存储子问题的最优解。从上往下分析问题,从下往上求解问题。
上面的几个条件可以看出,属于动态规划问题。
本题思路:
- 定义一个数组P,用来存储长度为n的绳子剪成若干段后各段长度乘积的最大值。
- 对于第一刀,我们有n-1种可能的选择,可推导出
p[n]=max{p[i]*p[n-i]}
; - 很明显这是一个从上至下的递归,但是这个递归存在很多重复的计算,所以使用
至下而上的动态规划
,将子问题的最优解保存。 - 注意绳子剪成长度为i和n-i的乘积与n-1和i是相同的;
- 注意不符合切割条件的输入n,以及输入为2、3长度时的结果,因为题中规定m>1。
//方法2:动态规划
//18ms 9736k
public int cutRope(int target){
if(target < 1)
return 0;
if(target == 2)
return 1;
if(target == 3)
return 2;
int[] P = new int[target + 1];//定义一个长度为target+1的数组,比target的长度大1,可以避免越界的问题,用来存放每个长度所对应的最大值
P[1] = 1;
P[2] = 2;
P[3] = 3;
for(int i = 4; i <= target; i++){ //当target的值大于4时,自底向上分别计算每个值并存放到数组中
int max = 0;
for(int j = 1; j <= i / 2; j++){ //每个值只需要计算到该值的一半即可
int temp = P[j] * P[i - j];//这里是用i-j不是target-j,计算的是每个值
max = temp > max ? temp : max;
}
P[i] = max;
}
return P[target];
}