-
题目:
本题和343.整数拆分一模一样;
一根长为n的绳子剪成m段,求每段绳子乘积的最大值;
m > 1 && n > 1; -
思路:
1.把绳子尽可能分成长度为3的小段,乘积最大;O(n):前面都是常数操作,后面while处循环n/3次,O(1)
证明:n = n1 + n2 + …,其中某一段长度为ni;
若ni>=5,则分成2和3乘积更大;
若ni4,则分成2和2乘积不变;
若ni1,显然长度为1的段会使乘积更小,例如3分成1和2;
若3段2的,不如2段3的大,因此3比2更好;
因此,优先分为3;若n%3==1,则分出来一个2+2,乘积为4;若n%3 ==2,则分出来一个2;其余只剩下3的倍数;
class Solution {
public:
int cuttingRope(int n) {//尽可能分成3,不够3的分成2
if (n <= 3) return 1 * (n - 1); //题目说m>1,即最少分两段, 则n == 2时最大值是分成1和1,n==3时最大值是分成1和2;
int res = 1;//乘积初始值
//先把n变成3的倍数
if (n % 3 == 1) res *= 4, n -= 4;//分出来4
if (n % 3 == 2) res *= 2, n-= 2;//分出来2
while (n) {//每3一段
res *= 3;
n -= 3;
}
return res;
}
};
2.DP:
设置一个数组dp[n+1],dp[ i ]存储绳子长度为i 时的最大乘积。依题意,绳子至少被剪一次,所以绳子长度最小为2。外层for循环从绳长为i=2的情况开始依次计算,直到计算到绳长为n的情况。(由于n==2时,递推式中会出现dp[1],但绳子至少分两段,不符合题意,因此直接算出dp[2]的值为1,外层循环i从3开始更佳)
内层for循环:当绳长为i时,由于已知至少剪一刀,我们索性假设第一刀剪在长度为j的位置(即第一段绳子长度为j)。剩下的那段长度为( i - j )的绳子就变成了“可剪可不剪”。那究竟是“不剪了”得到的乘积大呢,还是“继续剪余下的这段”得到乘积更大?我们不知道,所以需要两种情况都计算一下进行比较。其中,“不剪了”得到的乘积是j * ( i - j ),“继续剪”得到的乘积是j * dp[ i - j ]。取其中的较大值,就是“第一剪在j位置”能得到的最大乘积。而第一剪的所有可能位置是1,2,…,i-1。依次计算所有可能情况,取最大值即为dp[ i ]的值。
由上述过程可知,只有先依次计算出dp[2],dp[3],…的值,才能得到dp[n]的值。此为动态规划。
class Solution {
public:
int cuttingRope(int n) {
//初始化 dp数组:
//1、为了条理清晰,我们初始化数组长度为 n+1(0下标无用)
//2、根据题意,初始化dp[2]=1,且i从3开始;
vector<int> dp(n + 1);//dp[i]表示绳子长度为i时的最大乘积
dp[2] = 1 * 1;
for (int i = 3; i <= n; ++i) {//外层循环表示求绳子长度为i时的最大乘积dp[i]
for (int j = 1; j <= i / 2; ++j) {//枚举长度为i的绳子的第一段的长度
dp[i] = max(dp[i] , max(j * (i - j), j * dp[i - j]));//因为i-j<i,因此在计算dp[i]时需要用到的dp[i-j]一定在之前算过了
}
}
return dp[n];
}
};
- 总结:
方法1很牛B,方法2的DP很好的贯彻了DP5步法的分析过程,特别是dp数组的下标及对应含义,和dp数组的初始化要分析清楚;
怕忘,再刷