文章目录
- 题目
- 数论
- 思路
- 代码
- 复杂度分析
- 动规一
- 思路
- 代码
- 动规二
- 思路
- 代码
- 对最终结果取模1e9+7
- 思路
- 代码
题目
给你一根长度为 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
来源:力扣(LeetCode)
数论
数学的魅力被展现的淋漓尽致~
思路
- 除非不得已的情况,否则不要剪成长度为1的段(1乘n等于n,对于最大乘积没有提升)。
- 任何大于1的数都可由2和3相加组成(根据奇偶证明)。
- 因为
2*2=1*4
,2*3>1*5
, 所以将数字拆成2和3,能得到的积最大。(4和5还是拆分成2、3更有利) - 因为
2*2*2<3*3
, 所以3越多积越大
代码
class Solution {
public:int cuttingRope(int n) {return n <= 3? n - 1 : pow(3, n / 3) * 4 / (4 - n % 3);}
};
复杂度分析
时间复杂度:O(log(n/3))
空间复杂度:O(1)
动规一
这个版本比较好理解。
思路
- 每段都应该为3或者2(证明段应该为2或3不再赘述)。
- 将
n=2
和n=3
时最大乘积作为不具有规律的特殊值直接返回(因为n<=3时无法切分成3,不具有第一点所说的规律)。 - 动态规划数组dp中
下标为n
的位置保存着长度为n的绳子的最大乘积
(n=2,3时不再此列,已做特殊处理)。 - dp数组的长度:由于定义
dp[i]
为绳长度为i
时的结果,因此dp[n]
为最后一项,所以数组的总长度为n + 1
。 - dp下标为1,2,3中保存的就是1,2,3,其含义是 当n>3时,3已经不用在切分了, 切成1,2的话最大乘积也就是2,不切的话得到3,比切了得到的2更大(dp[2]同理)。
代码
class Solution {
public:int cuttingRope(int n) {if(3 >= n){return n-1;}vector<int> dp(n+1,0);dp[1] = 1;dp[2] = 2;dp[3] = 3;for(int i = 4; i <= n; i++){ // n > 3的处理// j<=i/2是因为1*3和3*1是一样的,没必要计算在内,只要计算到1*3和2*2就好了for(int j = 2; j <= i/2; j++){// 剪一段长度为1的绳子对乘积的提升没有作用// 因此从2开始剪dp[i] = max(dp[i], dp[j] * dp[i-j]);}}return dp[n];}
};
动规二
有一点抽象,但是不做特殊值的处理,属于力求包含所有规律的“正规”动规。
思路
- 将
n=2
的值作为历史值,开始求规律。 max(dp[i-j], i-j)
对于这一步可能是最难理解的,其实这只是为了处理n=3
的特殊情况,逻辑思想是: 减去j
后,剩余部分可以剪也可以不剪。如果不剪,则得到的长度乘积为j * (i - j)
;如果剪,得到的长度为j * dp[i - j]
。两者取最大值。
为什么说这只是为了处理
n=3
的特殊情况呢?
其实在数论方法中我们已经提到过了——因为2*2=1*4,2*3>1*5, 所以将数字拆成2和3,能得到的积最大。(4和5还是拆分成2、3更有利)
,也就是仅对于 2(和3) ,有 dp[2] (dp[3])小于 2(3),在n>3
时,max(dp[i-j], i-j)
的结果是恒定的——dp[n] > n
。
代码
class Solution {
public:int cuttingRope(int n) {if(n == 2){return 1;}vector<int> dp(n+1);dp[2] = 1;for (int i=3; i<=n; ++i){ // n >= 3for (int j=1; j<=i/2; ++j){// j 减去的长度dp[i] = max(max(dp[i-j], i-j) * j, dp[i]);}}return dp[n];}
};
对最终结果取模1e9+7
思路
动规是用不了了,因为:
- 计算结果可能会超出
int/long
范围 - 取余之后max函数就不能用来比大小了(所有大于1e9+7的数取模后就小于1e9+7了,也就是
n=120
以后最大乘积的值都是固定的)
因此应该用贪心的思想。
代码
class Solution {
public:int cuttingRope(int n) {if(n<=3){return n - 1;}int flag = 1000000007;long sum = 1;while(n > 4){sum = (sum * 3) % flag;n -= 3;}return (sum * n) % flag;}
};