剑指offer-剪绳子
JZ14 剪绳子
题目地址
描述
给你一根长度为 n 的绳子,请把绳子剪成整数长的 m 段( m 、 n 都是整数, n > 1 并且 m > 1 ,m <= n ),每段绳子的长度记为 k[1],…,k[m] 。请问 k[1]k[2]…*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8 时,我们把它剪成长度分别为 2、3、3的三段,此时得到的最大乘积是18。
数据范围:2≤n≤60
进阶:空间复杂度 O(1) ,时间复杂度 O(n)
输入描述:
输入一个数n,意义见题面。
返回值描述:
输出答案。
示例1
输入:
8
返回值:
18
说明:
8 = 2 +3 +3 , 2*3*3=18
示例2
输入:
2
返回值:
1
说明:
m>1,所以切成两段长度是1的绳子
解题思路
本文提供六种解题思路:
- 数学法找规律,时间复杂度0(n),满足要求
- 暴力递归,时间复杂度0(n!),不满足要求
- 记忆性优化,时间复杂度0(n!),不满足要求
- 动态规划,时间复杂度0( n 2 n^2 n2),不满足要求
- 找规律,转换为数学问题,时间复杂度0(n),满足要求
- 对方法5(找规律)优化,时间复杂度0(1),满足要求
代码
import java.util.*;
public class Solution {
public int cutRope(int target) {
return method6(target);
}
//方法一:数学法找规律 0(n) √
public int method1(int target){
//对于和为target
//n个数和为target,
// 若target%n==0:每个数都为target/n
// 若target%n!=0:target%n个数为target/n + 1,剩余为target/n
int maxVal=Integer.MIN_VALUE;
for(int n=2;n<=target;n++){
if(target%n==0){
int tmp=pow(target/n,n);
if(tmp>maxVal) maxVal=tmp;
else break;
System.out.println(n+":"+tmp);
}else{
int tmp=pow(target/n+1,target%n)*pow(target/n,n-target%n);
if(tmp>maxVal) maxVal=tmp;
else break;
//System.out.println(n+":"+tmp+"/"+(target/n+1)+"/"+target%n+"/"+target/n+"/"+(n-target%n));
}
//System.out.println(n+":"+maxVal);
}
return maxVal;
}
//a^b
public int pow(int a,int b){
int res=1;
for(int i=1;i<=b;i++){
res*=a;
}
return res;
}
//方法二:暴力递归 0(n!) × 时间复杂度过高
public int method2(int target){
if(target<4) return target-1;
else return dfs(target);
}
public int dfs(int n){
if(n<=4) return n;
int cnt=-1;
for(int i=1;i<n;i++){
cnt=Math.max(cnt,i*dfs(n-i));
}
return cnt;
}
//方法三:记忆化搜索 0(n!)优化 × 时间复杂度过高
public int method3(int target){
if(target<4) return target-1;
int[] mem=new int[target+1];//init is zero
return dfs_mem(target,mem);
}
public int dfs_mem(int n,int[] mem){
if(n<=4) return n;
if(mem[n]!=0) return mem[n];
int cnt=-1;
for(int i=1;i<n;i++){
cnt=Math.max(cnt,i*dfs_mem(n-i,mem));
}
return mem[n]=cnt;
}
//方法四:动态规划0(n^2) √
public int method4(int target){
if(target<4) return target-1;
int[] dp=new int[target+1];
dp[1]=1;dp[2]=2;dp[3]=3;dp[4]=4;
for(int i=5;i<=target;i++){
int cnt=-1;
for(int j=1;j<i;j++){
cnt=Math.max(cnt,j*dp[i-j]);
}
dp[i]=cnt;
}
return dp[target];
}
//方法五:找规律求解--数学问题:0(n)
//对表达式y = (n/x)^x求导后发现
//当x=e时,y取最大值,因此保证没根绳子长度在2和3,3为主
public int method5(int target){
if(target<4) return target-1;
int res=1;
while(target>4){
target-=3;
res*=3;
}
return res*target;
}
//方法六:对方法五优化:0(1)√
public int method6(int target){
if(target<4) return target-1;
if(target%3==0){//全部剪成长度为3的段
return (int)Math.pow(3,target/3);
}else if(target%3==1){//一段长度为4,其余长度为3
return 4*(int)Math.pow(3,(target-4)/3);
}else{//一段长度为2,其余长度为3
return 2*(int)Math.pow(3,target/3);
}
}
}
JZ83 剪绳子(进阶版)
题目地址
描述
给你一根长度为 n 的绳子,请把绳子剪成整数长的 m 段( m 、 n 都是整数, n > 1 并且 m > 1 ,m <= n ),每段绳子的长度记为 k[1],…,k[m] 。请问 k[1]k[2]…*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为 2、3、3 的三段,此时得到的最大乘积是18。
由于答案过大,请对 998244353 取模。
数据范围:2 ≤ n ≤ 10^14
进阶:空间复杂度 O(1) , 时间复杂度 O(logn)
示例1
输入:
4
返回值:
4
说明:
拆分成 2 个长度为 2 的绳子,2 * 2 = 4
或者直接不剪,答案也是4。
示例2
输入:
5
返回值:
6
说明:
剪成一个长度为 2 的绳子和一个长度为 3 的绳子,答案为2*3=6
示例3
输入:
874520
返回值:
908070737
设计思路
采用上面一题的方法六(时间复杂度0(1)),但是由于数据规模过大,幂次运算时,采取快速幂的形式,使得时间复杂度变为0(logn)
代码
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* @param number long长整型
* @return long长整型
*/
long mod=998244353;
public long cutRope (long target) {
// write code here
return method(target);
}
public long method(long target){
if(target<4) return target-1;
if(target%3==0){//全部剪成长度为3的段
return pow(3,target/3)%mod;
}else if(target%3==1){//一段长度为4,其余长度为3
return 4*pow(3,(target-4)/3)%mod;
}else{//一段长度为2,其余长度为3
return 2*pow(3,target/3)%mod;
}
}
public long pow(long a,long b){ //a^b 采用快速幂0(logn)
long res=1;
while(b!=0){
if((b&1)==1){
res=res*a%mod;
}
a=a*a%mod;
b>>>=1;//无符号右移
}
return res%mod;
}
}