一、题目

剑指 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:

1
2
3
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例2:

1
2
3
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

二、分析

方法1 暴力+动态规划

这道题剪绳子,我们用动态规划的思想去做。

  • 假设动态规划 f(i)表示i长的绳子切割后最大长度 $f(i)=max((i-k)k,f(i-k)k))其中k=[2,i)$,k表示第一段割下来的长度
  • 依次从i长的绳子里面割j长的绳子.剩下的$(i-j)$长绳子可以不再分割:最大乘积就为$(i - j) * j$。
  • 也可以再次分割,最大乘积就为:$j * dp[i - j]$。两者取最大。
  • 而最外面的max是因为第一段可以割不同的$j$(暴力思想),需要不断更新。

方法2 贪心算法

  • 要考虑尽可能每一段都要分隔成3的长度。

  • 将特殊情况绳长小于等于3情况的去除掉

  • 将绳长大于4的情况,不断切割下3的绳子,可能最后剩下4,3,2,剩下的正好作为单独一段。

  • 注意:这里为什么条件是大于4,而不是大于当于4。因为如果等于4,那么4就会在被分隔成3+1 ,这两段乘积3x1=3。我们知道4切割后最大的乘积应该是2x2=4。因此我们这里干脆将条件设为大于4,那么最后就可能剩下一段绳子长度为4。正好符合4切割后的最大乘积4。

三、代码

方法1 暴力+动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
* 题目:剑指 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
* 实现:暴力+动态规划 f(i)表示i长的绳子切割后最大长度 f(i)=max((i-k)*k,f(i-k)*k)) 其中k=[2,i),表示第一段割下来的长度
1.依次从i长的绳子里面割j长的绳子.剩下的(i-j)长绳子可以不再分割:最大乘积(i - j) * j。
2.也可以再次分割:j * dp[i - j]。两者取最大。
3.而外面max是因为第一段可以割不同的j(暴力思想),需要不断更新。
* 复杂度:时间O(n^2): 循环次数 1+2+....+n-2=(n-1)*(n-2)/2
* 空间 O(n):线性辅助空间
*/
int cuttingRope(int n) {
int* dp = new int[n + 1]{ 0 };
dp[2] = 1;//初始值
for (int i = 3; i <= n; i++)
{
//依次从i长的绳子里面割j长的绳子.剩下的(i-j)长绳子可以不再分割:最大乘积(i - j) * j。
//也可以再次分割:j * dp[i - j]。两者取最大。
//而外面max是因为第一段可以割不同的j,需要不断更新。
for (int j = 2; j < i; j++)
{
dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j]));
}
}
return dp[n];
}

方法2 贪心算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 题目:剑指 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
* 实现:贪心算法。每一段都要分隔成3
* 复杂度:时间O(logn):大约执行x次,3^x=n,所以x=log3(n)
* 空间 O(1):常数辅助空间
*/
int cuttingRopeA(int n) {
if (n <= 3) return n - 1;
int res = 1;
while (n > 4) {
res = res * 3;
n = n - 3;
}
return res * n;
}

四、总结

这道题核心是想到用动态规划的思想来做。可能第二种贪心算法在有限时间内不容易证明出来,但是第一种动态规划思想还是比较常见,应该要会掌握。