题目一
解法一:动态规划
class Solution {
public int cuttingRope(int n) {
if(n <= 1) return 0; // 题目要求至少需要切一次,长度小于2没法切
if(n == 2) return 1; // 1*1=2
if(n == 3) return 2; // 1*2=2
int[] dp = new int[n+1]; // dp数组中第 i-1个元素存放把长度为 i的绳子剪成若干段之后各段长度乘积的最大值,即各个子问题的最优解。
dp[0] = 0; // 长度为1的绳子对应结果
dp[1] = 1; // 长度为2的绳子对应结果
dp[2] = 2; // 长度为3的绳子对应结果
int max_num;
for(int i=3; i<=n; i++) {
max_num = 0; // 保留内循环的最大值
for(int j=1; j<=i/2; j++) { // 前面切j,后面剩 i-j, 等价于前面切 i-j,后面剩 j。因此 j只需遍历到 i/2这里就可以
// 将当前长度为 i的绳子从 j处剪一下,得到 j和 i-j的两部分,比较继续剪 i-j部分(j*dp[i-j])和不继续剪(j*(i-j))的乘积较大者。
int tmp = Math.max(j*dp[i-j], j*(i-j));
if(max_num < tmp) max_num = tmp; // 如果内循环本轮 j对应的乘积最大值大于之前的 j对应的乘积最大值,则更新
}
dp[i] = max_num; // 内循环结束,将搜索到的最大值存放到 dp[i]中
}
return dp[n];
}
}
解法二:DP优化
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n-1;
int[] dp = new int[n+1];
dp[1] = 1;
dp[2] = 2;
dp[3] = 3;
for(int i=4; i<=n; i++) {
dp[i] = (2*dp[i-2])>(3*dp[i-3]) ? 2*dp[i-2] : 3*dp[i-3];
}
return dp[n];
}
}
解法三:贪心算法
class Solution {
public int cuttingRope(int n) {
if(n <=3) return n-1;
int res = 1;
while(n >4){
res = res*3;
n -= 3;
}
return res*n;
}
}
解法四:数学法
详细题解参考这篇文章
class Solution {
public int cuttingRope(int n) {
/*
最优切分子段长度为 3
当 n≤3 时,应不切分,但由于题目要求至少剪一次,因此只能剪出一段最小的长度为 1的绳子,即返回 n-1。
*/
if(n <= 3) return n-1;
// 求 n除以 3的整数部分 a(可以得到多少个长度为3的子段)和余数部分 b(留下的最后一段绳子长度只可能为0,1,2三种情况)
int a = n/3;
int b = n%3;
if(b == 0) return (int)Math.pow(3, a); // 可以整除3,直接求3的a次幂
else if(b == 1) return (int)Math.pow(3, a-1)*4; // 若最后一段绳子长度 b=1, 则应把一份 3+1替换为 2+2,因为 2×2>3×1。返回3^(a-1)*2*2
else return (int)Math.pow(3, a)*2; // 若最后一段绳子长度 b=2, 则应保留,不再拆分为 1+1。返回3^(a)*2
}
}
题目二(考虑大数问题)
此题和上一题的唯一不同在于本题目涉及 大数越界情况下的求余问题 。
解法一:数学法
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n-1;
int a = n/3, b = n%3, p = 1000000007;
long x = 3; // 至少要保证 x可以正确存储 1000000007^2
if(b == 0) return (int)remainder(x, a, p); // 可以整除3,直接求3的a次幂
if(b == 1) return (int)(remainder(x, a-1, p)*4 %p); // 若最后一段绳子长度 b=1, 则应把一份 3+1替换为 2+2
return (int)(remainder(3, a, p)*2 %p); // 若最后一段绳子长度 b=2, 则应保留,不再拆分为 1+1。返回3^(a)*2
}
// 快速幂求余法 求解 (x^a) % p
public long remainder(long x, int a, int p) {
long res = 1;
while(a > 0) { // 快速求幂法思路,同时每次取余
if(a % 2 == 1) // 判断为奇数次幂时,将多出的一项乘入 res中
res = (res*x) % p;
x = (x*x) % p;
a /= 2; // 对指数不断二分
}
return res;
}
}
解法二:贪心算法
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n-1;
if(n == 4) return 4; // 4最优是拆成 2+2,但 2*2结果等同4本身
int mod = (int)1e9+7;
long res = 1;
while(n >= 5) { // 循环求余法,只考虑4以上的长度是因为,当n=4时,最大乘积2x2和自身是等同的
res *= 3;
res %= mod;
n -= 3; // 每次都优先剪出长度为3的子段
}
// 尽可能多地剪出长度为3的子段,直到绳子长度<5,则不需要再切分了,直接保留该段,故乘上最后剩余的n
return (int)(res * n % mod);
}
}