剑指 Offer 14- I. 剪绳子
个人思路
此题基于整数拆分,是整数拆分的变形。
题意
将绳子结为n段,对每段进行乘积并求出最大值。
思路
对给定长度n进行裁剪,其实可以分为两种情况:(f(n)表示将n裁剪为多个的最大乘积)
- 将绳子n分解成两段
f(n) = i * (n - i) - 将绳子n分解成两段以上的情况
也就是将i和(n-i)进一步分解(即,对i进行分解,对n-i进行分解,对他们两个均进行分解)
当i取值范围在[1,n-1]的时候,i与n-i的取值是对称的,因此 (n-i)*f(i) 与 f(n-i)*i 的取值范围是相同的,而且同时包含了f(i) * f(n-i),因此整个表达式可以整合为 f(n) = i * f(n-i)
整合:f(n) = max(情况1,情况2),即f(n) = max(i * (n - i), i * f(n - i))
dp[i]状态数组表示:数字i经拆分后的最大乘积为dp[i]
自顶向下分析:
- 求最优解:求乘积的最大值
- 最优子结构:截取后的每段绳子的乘积取最大值才能保证整条绳子裁剪后取最大值
- 重叠子问题:假设绳长为10,截取后为f(10) = f(6) * f(4),要求得f(6)和f(4),f(2)便为他们的重叠子问题
自下而上解决问题:
- 边界:dp[0],dp[1],dp[2]是已知固定的
- 状态转移:上述两种裁剪方式取最大值,并和dp[i]本身比较,f(n) = max(i * (n - i), i * f(n - i)) ;dp[i] = max(dp[i],f(n))
二刷——备忘录方法
二刷对于该题的思路又不是很清晰了,总的来说有几个问题
- 转化为整数拆分问题,如何转化的思路不是很清晰
- 在取最值比较时,如上图所示,是存在对称情况的,这个没有理解到位
- 用备忘录方法时,递归代码写得不是很顺利
- 递归代码中是用res存储dp[n]的数值,而不是用dp[i]存储,如果用dp[i]表示每次都要进行更新的
int temp; int res = 0; for(int i = 2; i <= n; ++i){ temp = max(i * DP(n - i), i * (n - i)); res = max(res, temp); } dp[n] = res;
注意
- dp[i]:表示数字i经拆分后的最大乘积为dp[i]
- 注意要与dp[i]本身进行比较取最大值
个人思路代码
class Solution {
public:
//dp[i]表示数字i经拆分后的最大乘积为dp[i]
int dp[60];
int cuttingRope(int n) {
dp[0] = 0;
dp[1] = dp[2] = 1;
for(int i = 3; i <= n; ++i){
for(int j = 1; j <= i - 1; ++j){
int temp = max(j * (i - j), j * dp[i - j]);
dp[i] = max(dp[i], temp);
}
}
return dp[n];
}
};
二刷——备忘录
class Solution {
public:
int dp[60];
int DP(int n){
if(dp[n] != 0){
return dp[n];
}
int temp;
int res = 0;
for(int i = 2; i <= n; ++i){
temp = max(i * DP(n - i), i * (n - i));
res = max(res, temp);
}
dp[n] = res;
return dp[n];
}
int cuttingRope(int n) {
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
return DP(n);
}
};