题目描述
给你一根长度为 n 的绳子,请把绳子减成 m 段(m、n都是整数,n > 1 并且 m >1),每段绳子的长度记为k[0], k[1],…,k[m]。请问k[0]*k[1]k[2]…*k[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成的长度为2、3、3三段,此时得到的最大乘积是18。
测试用例
- 功能测试(绳子的初始长度大于5)
- 边界值测试(绳子的初始长度分别为0、1、2、3、4)
题目考点
- 考察应聘者的抽象建模能力。应聘者需要把一个具体的场景抽象成一个能够用动态规划或者贪婪算法解决的模型。
- 考察应聘者对动态规划和贪婪算法的理解。能够灵活运用动态规划解决问题的关键是具备从上到下分析问题、从下到上解决问题的能力,而灵活运用贪婪算法则需要扎实的数学基本功。
解题思路
动态规划
设 f(n) 代表长度为n的绳子剪成若干段的最大乘积,如果第一刀下去,第一段长度是 i ,那么剩下的长度就是 n-i ,那么f(n)=max{f(i)f(n-i)}。假设f(i) 确定,我们就要继续求f(n-i)的最优解,我们可以看出剪绳子是最优解问题,其次,大问题包含小问题,并且大问题的最优解包含着小问题的最优解,所以可以使用动态规划求解问题,并且从小到大求解,把小问题的最优解记录在数组中,求大问题最优解时就可以直接获取,避免重复计算。
对于的正整数 n,当 n≥2 时,可以拆分成至少两个正整数的和。令 k 是拆分出的第一个正整数,则剩下的部分是 n−k,n−k 可以不继续拆分,或者继续拆分成至少两个正整数的和。由于每个正整数对应的最大乘积取决于比它小的正整数对应的最大乘积,因此可以使用动态规划求解。
dp数组的含义: dp[i] 表示将正整数 i 拆分成至少两个正整数的和之后,这些正整数的最大乘积。
边界条件: 0 不是正整数,1 是最小的正整数,0 和 1 都不能拆分,因此 dp[0]=dp[1]=0。
状态转移方程:
当 i≥2 时,假设对正整数 i 拆分出的第一个正整数是 j(1≤j<i),则有以下两种方案:
将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j×(i−j);
将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j×dp[i−j]。
因此,当 j 固定时,有 dp[i]=max(j×(i−j),j×dp[i−j])。由于 j 的取值范围是 1 到 i−1,需要遍历所有的 j 得到 dp[i] 的最大值,因此可以得到状态转移方程如下:
d
p
[
i
]
=
m
a
x
1
<
=
j
<
i
(
j
×
(
i
−
j
)
,
j
×
d
p
[
i
−
j
]
)
dp[i] = \underset{1<=j<i}{max}(j \times (i-j),j \times dp[i-j])
dp[i]=1<=j<imax(j×(i−j),j×dp[i−j])
最终得到 dp[n] 的值即为将正整数 n 拆分成至少两个正整数的和之后,这些正整数的最大乘积。
贪婪算法
尽可能多剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现,如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。
证明:
当 n >= 5 时,所有f(n)都可以表示为3(n - 3)或者2(n - 2),又因为3(n - 3) - 2(n - 2) = n - 5 >= 0。因此把长度大于 5 的绳子切成两段,令其中一段长度为 3 可以使得两段的乘积最大。
当 n = 4时,拆成 2 * 2 最大。
代码
package com.offer._14_1;
/**
* 动态规划
* @author :jhys
* @date :Created in 2020/12/2 17:20
* @Description :
*/
public class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n + 1];
for (int i = 2; i <= n; i++) {
for (int j = 1; j < i; j++) {
dp[i] = Math.max(dp[i],Math.max(j * (i - j),j * dp[i - j]));
}
}
return dp[n];
}
}