给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
参考链接:https://www.jianshu.com/p/65b39734430c
思路:
这种题型一般用动态规划或者贪心算法进行解决
动态规划
动态规划:需要考虑两个条件:1、该问题是否可以分解成多个小问题进行求解2、结果是否需要重复计算。
这两个条件在这里都是满足的:例如绳子的长度是 12 的时候,一刀切下去变成 2 和 10,10 就成了一个子问题。10 还需要一刀切下去变成 2 和 8,这样 2 就重复了一次。虽说把 12 切成 2 和 10 不一定是最好的方案,但是我的目的在于证明子问题是有重复情况发生的。这个时候可以把子问题得到的结果提前记录下来,从而尽量避免无意义的重复,写法也就不是函数递归了,能够获得比较好的算法。在确定可以使用动态规划的时候,我们需要明确自己的目的:我们要找到最优的子问题划分。把 12 切成 2 和 10 好呢,还是切成 3 和 9 好呢? 3 和 9 比 2 和 10 要好。但是 3 和 9 就一定是最好的吗?39<48,那么 4 和 8 就一定最好吗?你会发现,从上到下的分析,并不容易得到正确答案。那么我们应该换一种思路,从下到上的分析:
假设绳子总共有 2 厘米长,由于必须剪一刀,乘积是 1。
假设绳子总共有 3 厘米长,那么乘积是 2。
假设绳子总共有 4 厘米长,剪成 2 和 2 要比 1 和 3 强,乘积是 4。注意,我们可以只剪一刀,这样子条件已经满足了,剪出来的 3 不需要再剪一刀了,因为再剪一刀反而让乘积变小了。
假设绳子总共有 5 厘米长,剪成 2 和 3 要比 1 和 4 强。同样,剪出来的 3 和 4 不需要再剪一刀了,因为再剪一刀反而让乘积变小了。
假设绳子总共有 6 厘米长,剪成 3 和 3 要比 2 和 4 强。
分析到这里的时候,我们其实已经有一些子问题的结果了。剪出来的绳子片段:
- 长度为 1 的时候,它最高能贡献的乘积因子是 1;
- 长度为 2 的时候,它最高能贡献的乘积因子是 2;
- 长度为 3的时候,它最高能贡献的乘积因子是 3,因为之前已经剪过一刀了,所以不需要把 3 再剪一次;
- 长度为 4 的时候,它最高能贡献的乘积因子是4,这个时候多剪一刀与不剪是没区别的;
- 长度为 5 的时候,它最高能贡献的乘积因子是 6,如果这个片段不剪的话能贡献 5,剪了的话能贡献当然选择再剪一刀;
- 长度为 6 的时候,它最高能贡献的乘积因子是 9,不剪的话能贡献 6,剪了的话能贡献3*3=9,当然选择再剪一刀;
发现没有,上面的剩余长度为 4 的问题,可以理解成两个剩余长度为 2 的乘积;剩余长度为 5 的问题,可以理解为两个剩余长度为 2 和 3 的乘积;剩余长度为 6 的问题,可以理解为两个剩余长度为 3 的乘积……如果用函数的思想,用代表绳子剩余长度为 n 的时候能够提供的最大贡献,
代码如下:
#动态规划
class Solution:
def cutRope(self, number):
# write code here
if number < 2:
return 0
if number == 2:
return 1
if number == 3:
return 2
product = [0]*(number+1)
product[2] = 2
product[3] = 3 #这里的 2 指的是剩下了一段长度为 2 的绳子,可以不剪
for i in range(4,number+1):
max_ = 0
for j in range(2,i/2+1):
max_ =max(product[j]*product[i-j],max_)
product[i] = max_
return product[number]
贪心算法
#贪婪算法
class Solution:
def cutRope(self, number):
# write code here
if number < 2:
return 0
if number == 2:
return 1
if number == 3:
return 2
three_num = number/3
if (number - three_num*3)==1:
three_num -= 1 #当还剩余4时,则不考虑在剪绳子,而是直接乘4,因为3*1<4
two_num = (number - three_num*3)/2
return pow(3,three_num)*pow(2,two_num)