Part1. 我的思路和代码
把绳子剪成若干段后,这若干段各自又可以剪成若干段,如此循环,属于层层大问题包含小问题、小问题的最优解和大问题的最优解相关的情形
,我想到了动态规划
的方式:定义一个数组dp,假设绳子长度为i时的最大乘积已经计算好并保存到dp[i],那么当绳子长度大于i时,剪出的若干段如果包括长度为i的段,就可以利用dp[i]计算,免去了计算这段长度为i的绳子的最大乘积。
由题目得知绳子必须被剪为至少2段
(长度分别为x、y),不能不剪。而这2段绳子各自或者不剪,或者剪了
,当不剪时
为最终的乘积提供的因数就分别是绳子长度本身x、y,当剪了时
提供的因数分别是dp[x]、dp[y],要使最后乘积最大,因此计算x和dp[x]的最大值max_left、y和dp[y]的最大值max_right,将二者相乘即为乘积最大的结果。另外,将绳子剪成2段有多种方式
,数学关系上为x+y=n,用一个循环遍历这些情况即可,x和y有不同的组合,因此采用自底向上的计算方式
,绳子长度为1、2时最大乘积容易得到,从长度为3时开始计算直到长度为n。
该做法需要双循环,时间复杂度为O(n^2),题目提示还可以是O(n),但没有想到这种时间复杂度的做法,并且n最大值为60,所以用O(n^2)的方法也可以在1s的时间限制内完成计算
。
int cutRope(int n) {
int ans;
int dp[61] = {0};
dp[1] = 1;
dp[2] = 1;
for(int len=3; len<=n; len++){
for(int i=1; i<=len/2; i++){
int right;
int max_left;
int max_right;
right = len-i;
max_left = i>dp[i]? i : dp[i];
max_right = right>dp[right] ? right : dp[right];
if(max_left*max_right > dp[len]){
dp[len] = max_left*max_right;
}
}
}
ans = dp[n];
return ans;
}
Part2. 其他做法
贪婪算法
是书中给出的方法,尽可能多剪出长度为3的绳子
,假设剪出count段。此时,如果剪出count-1段时剩下的绳子长度为4
,那么就不将这长度为4的绳子剪成长度为3、1的两部分,而是乘积更大的2、2两部分
。这种做法是通过n>=5时,2(n-2)>n且3(n-3)>n且3(n-3)>=2(n-2)证明正确性
的,但目前我不知道为什么能证明。按照此思路代码简洁许多,但我的写法和书上的有所不同,对“剩下的绳子长度不为4”的情况用了更多的if-else语句。
int cutRope(int n) {
int ans;
int count3 = 0;
int left;
if(n==2){
ans = 1;
}
else if(n==3){
ans = 3;
}
else if(n==4){
ans = 2;
}
else{
count3 = n / 3;
left = n % 3;
if(left == 0){
ans = pow(3, count3);
}
else if(left == 1){
ans = pow(3, count3-1) * (2*2);
}
else{
ans = pow(3, count3) * 2;
}
}
return ans;
}
特别记录:这样感觉像是用公式解决一个题目,时间复杂度最大的部分或许就是调用pow()函数的步骤,该函数由于编译器的不同执行方式也不同,时间复杂度可能为O(n)或O(logn)。
Part3. 心得体会
- 动态规划的题目非常
考验思维
。 - 对我个人而言是一种思维瓶颈,但
数学知识
的运用确实可以做出更高效的算法。