面试题13:剪绳子
题目描述
给你一根长度为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。
问题分析
(1)思路一:动态规划
遇到问题,先分析问题,由分析的结果确定所运用的算法。
一般而言,我们都会在纸上动笔画画,罗列一些基本的情况。
题中说n>1并且m>1,那么
- 当n=2时,最大乘积只能为1,只有一种情况1*1;
- 当n=3时,最大乘积只能为2,有两种情况,1*2,1*1*1;
- 当n=4时,可以分为如下几种情况:1*1*1*1,1*2*1,1*3,2*2,最大乘积为4;
- 当n=x时,剪第一刀的时候,我们有x-1种可能的选择,也就是剪出的一段绳子的长度为1、2、...、x-1
参考n= x的情况,如果把长度n绳子的最大乘积记为f(n),则有:f(n)=max(f(i)*f(n-i)),0<i<n。
当你分析到这个的时候,思路就得到了一层的突破:从下往上推,先算小的问题,再算大的问题,大的问题通过寻找小问题的最优组合得到。这个不就是动态规划
吗?
动态规划算法的定义是通过拆分
问题,定义问题状态和状态之间的关系
,使得问题能够以递推(或者说分治)的方式去解决。
既然写到了动态规划,那我就将我所知道的都告诉你:
关于动态规划算法定义的理解
- 关于拆分问题 : 就是根据问题的性质把问题划分。(我们上述基本情况的讨论,最后得出n = x的普遍情况,走的就是这一步)
- 关于定义问题状态和状态之间的关系: 也就是上面拆分的步骤之间的关系,用一种量化的形式表现出来,这种形式一般可以说是递推式(我们最后得出的f(n)=max(f(i)*f(n-i)),0<i<n 就是走的这一步)
至于算法思想的理解,我们解答中探讨
(2)思路二:贪心
这个涉及到一个数学模型,原本的定义非常宏大和复杂,这里不多赘述,我们这里只是涉及到一丁点的应用,你有个印象即可。
我们之前也讨论了,刚才的递推式f(n)=max(f(i)*f(n-i)),0<i<n。 是以n =2、n =3 为基石的。
对于任意一个大于或者等于5的数字,都可分解为加数 2 和 3的和。(这个小学数学就能证明,不证明了哈)
递推式涉及到乘积,很容易注意且能证明3(length-3)>2(length-2),因此要想乘积最大,尽可能分出更多的加数3
除了上述所说的,我们还有一个特殊的数字就是4,当length=4的时候,13<22=4,所以length=4的时候不用再分。
局部最优能保证整体最优,贪心算法写起来要比动态规划简单多了。
问题解答
(1)动态规划算法的实现
/**
* @param length 输入的绳子的长度
* @return 乘积的最大值
* 动态规划的算法思想:
* 动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。
* 在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
*/
public int maxProductAfterCutting(int length) {
// 递推式: f(n)=max(f(i)*f(n-i)),0<i<n
if (length <= 1)
return 0;
if (length == 2)
return 1;
if (length == 3)
return 2;
// 存储最优解
int[] product = new int[length + 1];
// 下面几个不是乘积,因为其本身长度比乘积大
product[0] = 0;
product[1] = 1;
product[2] = 2;
product[3] = 3;
// 开始从下到上计算长度为i绳子的最大乘积值product[i]
for (int n = 4; n <= length; n++) {
int max = 0;
// 算不同子长度的乘积,找出最大的乘积
// 这就是算法中 找到 最优解 步骤
for (int i = 1; i <= n / 2; i++) {
if (max < product[i] * product[n - i])
max = product[i] * product[n - i];
}
// 把最优解保存下来(为了后面的使用的最优解,为局部最优解)
product[n] = max;
}
return product[length];
}
(2)贪心算法的实现
public int maxProductAfterCutting(int length) {
if (length <= 1)
return 0;
if (length == 2)
return 1;
if (length == 3)
return 2;
// 确定分解成一段段长度为3的绳子的个数
int timesOfThree = length / 3;
if (length - timesOfThree * 3 == 1) {
timesOfThree--;
}
int timesOfTwo = (length - timesOfThree * 3) / 2;
return (int) (Math.pow(3, timesOfThree) * Math.pow(2, timesOfTwo));
}
原文地址:https://www.cnblogs.com/JefferyChenXiao/p/12246300.html