剑指 Offer 14- I. 剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]k[1]...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
- 2 <= n <= 58
一、动态规划
首先使用动态规划解决问题,最重要的就是理解dp数组的含义。 此处的dp[i] 表示长度为i的绳子剪成m段后的最大乘积。初始化dp[2] = 1 表示如果一个绳子长度为2必然只能分成两段长度为1的绳子,两者的乘积为1。
- 然后尝试对绳子进行分割,如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪,代码中的j就是表示尝试剪的长度。
- 剪下一段后,剩余部分可以剪也可以不剪。如果不剪则得到的长度乘积为
j * (i - j)
。如果剪得到的长度为j * dp[i - j]
两者取最大值 - 不断修改剪的长度j (j范围为[2, i -1])。从所有结果中找到最大值即为dp[i]的结果。
- 从而有状态转移方程
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
class Solution {
public int cuttingRope(int n) {
// dp[i]表示长度为i的绳子被剪成m段,每段乘机的最大值
int[] dp = new int[n+1];
// 因为m>1,所以dp[2] = 1而不是2
dp[2] = 1;
// i表示绳子长度
for (int i=3;i<=n;++i){
// i-j >= 2。j代表第一次剪掉的长度,剪掉1没用,所以从2开始,剩余的长度i-j怎么减直接取dp[i-j]
for (int j=1;j<=i-2;++j){
// 这里因为m>1,所以dp[2] = 1而不是2,dp[3]不能是dp[2] * 1,这样答案是1,错误.
// 因此下面要添加Math.max(dp[i-j], i-j)。
dp[i] = Math.max(Math.max(dp[i-j], i-j) * j, dp[i]);
}
}
return dp[n];
}
}
二、贪心算法
这个做题思路直接引用K神吧,直接给跪了。
算法流程:
- 当 n ≤3 时,按照规则应不切分,但由于题目要求必须剪成 m>1 段,因此必须剪出一段长度为 11 的绳子,即返回 n - 1。
- 当 n>3时,求 nn 除以 33 的 整数部分 aa 和 余数部分 bb (即 n = 3a + b ),并分为以下三种情况:
- 当 b = 0 时,直接返回 3^a ;
- 当 b = 1时,要将一个 1 + 3转换为 2+2,因此返回 3^{a-1} ×4;
- 当 b = 2时,返回 3^a × 2。
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n - 1;
int a = n / 3, b = n % 3;
if(b == 0) return (int)Math.pow(3, a);
if(b == 1) return (int)Math.pow(3, a - 1) * 4;
return (int)Math.pow(3, a) * 2;
}
}
其实K神还有更详细的求导过程和推论过程,建议点参考链接详细再看看。