剑指offer第14题 剪绳子的多种解法



问题一:

问题描述:

给你一根长度为 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;
    }

提交结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥自在”

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值