问题一:
问题描述:
给你一根长度为 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)
链接:https://leetcode-cn.com/problems/jian-sheng-zi-lcof
解题思路:
解法一:递归解法
面对长度为n的绳子,我们可以每次剪出一段用于保留,然后对剩下的另一段继续进行裁剪。剪绳子时我们的可选范围就是绳子剩余的长度。比如剩余长度为5,我们可以剪出1,2,3,4;当然也可以选择整段保留。所以递归树中的分支是变化的。
解法二:记忆型递归
解法一会对同一个子问题多次求解,所以在效率上很慢,可以使用一个数组将递归中得到的结果进行保存;然后每次进入函数先判断是否有记录,如果有则直接返回结果,避免重复运算。
解法三:动态规划
根据解法二的启发,可以发现问题的结果是可以通过子问题推导出来的。我们定义一个数组,第i个元素表示绳子长度为n时的最大乘积。因为绳子的长度为1,2,3时最大乘积是可以看出来的。所以可以对数组进行初始化。
最重要的是对DP公式的推导;对于长队为i的绳子,可以剪去j个长度分成两段。然后最大乘积为:dp[i]*dp[i-j]
;为了保证得到最大乘积要依次对j进行尝试,范围为:1到i/2。因为j的取值大于i/2时相当于将之前的i和i-j进行交换,结果是相同的。所以循环到i/2即可。
上面的dp公式并不完全正确,因为初始化时dp[2]=1,但是在推导的时候剩余长度是2时,应该保留长度2,而不是分解成两个1;才能得到最大乘积。所以为了保证上面dp公式的正确性,在初始化时将dp[2]=2。但是注意当n=2时,的确应该返回1,所以要对n=2的情况单独处理。 同理可得,n=3时也应该单独处理,并且将dp[3] = 3。
解法四:3等分
根据数学推导证明(推导过程在此省略),将绳子尽量划分为长度为3时乘积最大,所以我们可以先求n对3求余的结果,需要注意的是余数为1时,此时最优的解法是将最后一段的长度保留为4。另外注意求余和除法不能同时使用,否则结果不一定正确。
代码实现:
解法一:递归
public static int cuttingRope(int n) {
return dfs(n);
}
//表示绳子长度为n时的最大乘积
private static int dfs(int n) {
if(n==0 || n==1){ //递归的出口,因为求的是乘积,所以不能返回0
return 1;
}
int v2 = 1;
for(int i=1; i<n; i++){ //每次剪绳子时都有n种选择,不能让i=0,这会导致死循环;保留一整段的情况可以放到循环中进行
v2 = Math.max(v2, i*dfs(n-i));
v2 = Math.max(v2, i*(n-i)); //将剩下的一段保留
}
return v2;
}
注:上述的解法一提交会超时
解法二:记忆型递归
static int[] arr;
public static int cuttingRope(int n) {
arr = new int[n+1];
return dfs(n);
}
//表示绳子长度为n时的最大乘积
private static int dfs(int n) {
if(n==0 || n==1){ //递归的出口,因为求的是乘积,所以不能返回0
return 1;
}
if(arr[n] != 0 ) {
return arr[n];
}
int v2 = 1;
for(int i=1; i<n; i++){ //每次剪绳子时都有n种选择,不能让i=0,这会导致死循环;保留一整段的情况可以放到循环中进行
v2 = Math.max(v2, i*dfs(n-i));
v2 = Math.max(v2, i*(n-i)); //将剩下的一段保留
}
arr[n] = v2; //返回结果前对结果进行保存
return v2;
}
解法二提交结果:
解法三:动态规划
public static int cuttingRope(int n) {
if(n<=3) return n-1;
int[] dp = new int[n+1];
dp[1] = 0;
dp[2] = 2; //绳子长度为2时,应该返回1,但是为了后面简化dp公式;n=2的情况单独处理
dp[3] = 3;
for(int i=4; i<=n; i++) {
for(int j=1; j<=i/2; j++) {
dp[i] = Math.max(dp[i], dp[j]*dp[i-j]);
}
}
return dp[n];
}
解法三的提交结果:
解法四:3等分
public static int cuttingRope(int n) {
if(n<=3) return n-1;
int sum = 1;
int m = n/3;
for(int i=0; i<m; i++) {
sum = sum * 3;
}
int c = n%3;
if(c==2) {
sum = sum * 2;
}else if(c==1) {
sum = sum/3;
sum = sum * 4;
}
return sum;
}
解法四提交结果:
问题二:
问题描述:
给你一根长度为 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。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
提示:
2 <= n <= 1000
解题思路:
这题和第一题主体是相同的,只是这一题要求的数据范围更大,且要求结果对1000000007求余。根据第一题的解法四进行改进,只需要注意求余和除法不能同时进行即可。
代码实现:
public static int cuttingRope(int n) {
if(n<=3) return n-1;
long sum = 1;
int m = n/3;
int c = n%3;
if(c==2) {
for(int i=0; i<m; i++) {
sum = sum * 3 % 1000000007;
}
sum = sum * 2 % 1000000007;
}else if(c==1) { //最后一段保留4,少乘以一个3
for(int i=0; i<m-1; i++) {
sum = sum * 3 % 1000000007;
}
sum = sum * 4 % 1000000007;
}else {
for(int i=0; i<m; i++) {
sum = sum * 3 % 1000000007;
}
}
return (int)sum;
}
提交结果: