剑指Offer:剪绳子(动态规划、贪婪算法)
问题描述
给你一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?
例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入描述:
输入一个数n,意义见题面。(2 <= n <= 60)
输出描述:
输出答案。
示例1
输入 8
输出 18
解题思路
本题有几个 可使用动态规划算法的明显特征:
(1)是求最优解问题,如最大值,最小值;
(2)该问题能够分解成若干个子问题,并且子问题之间有重叠的更小子问题。
通常按照如下 4 个步骤来 设计一个动态规划算法:
(1)刻画一个最优解的结构特征;
(2)递归地定义最优解的值;
(3)计算最优解的值,通常采用自底向上的方法;
(4)利用计算出的信息构造一个最优解。
以此题为例,
设长度为 n 的绳子剪切后的最大乘积为 f(n),
我们可以将问题看成两个子问题,即 f(n) = max(f(j)*f(n-j)) ,j 属于 (1, 2, …, n/2)
也就是说,我们要求 f(n),就只要求将 n 分成 n 、n-j 里面的最优分解就行了。
为什么 j 的边界是 n/2 呢? --> 因为将一个数分成两个数,最大的就是 n/2
**假设 n 为 11,**第一刀之后分为了 4-7,而 7 也可能再分成 1-5(7 的最大是 3-4,但过程中还是要比较 1-5 这种情况的),而上一步 4-7 中也需要求长度为 4 的问题的最大值。
可见,各个子问题之间是有重叠的,所以可以先计算小问题,存储下每个小问题的结果,逐步往上,求得大问题的最优解。
Java 代码(动态规划)
import java.lang.*;public class Solution {public int cutRope(int target) {if (target < 2) {return 0;}if (target == 2) {return 1;}if (target == 3) {return 2;}// ropes[i]表示剪i次时的最大乘积int[] ropes = new int[target + 1];ropes[1] = 1;ropes[2] = 2;ropes[3] = 3;int temp = 0;for (int i = 4; i <= target; i++) {int max = 0;// f(i) = max(f(j)*f(n-j)), j属于(1,2,...,n/2)for (int j = 1; j <= i / 2; j++) {temp = ropes[j] * ropes[i - j];max = Math.max(temp, max);}ropes[i] = max;}return ropes[target];} }复杂度分析:
- 时间复杂度:O(n^2)。
- 空间复杂度:O(n)。
Java 代码(贪婪算法)
另解(使用贪婪算法):
当 n<5 时,与动态规划的处理一致;
当n>=5时,尽可能多地剪长度为 3 的绳子,当剩下的绳子长度为 4 时,剪成 2-2;
例如:长度为 10 的绳子, 剪成 3-3-2-2
public int maxProductAfterCutting(int length) {if (length < 2) {return 0;}if (length == 2) {return 1;}if (length == 3) {return 2;}// 尽可能多地剪去长度为3的绳子段int timesOf3 = length / 3;// 当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子段// 此时更好的方法是把绳子剪成长度为2的两段,因为2x2>3x1if (length - timesOf3 * 3 == 1) {timesOf3 -= 1;}int timesOf2 = (length - timesOf3 * 3) / 2;return (int) (Math.pow(3, timesOf3) * Math.pow(2, timesOf2)); }复杂度分析:
- 时间复杂度:O(1)。
- 空间复杂度:O(1)。
虽然贪婪算法的时间复杂度低,但面试官一般都会要求证明
数学证明:
https://www.jianshu.com/p/0a13e48aa4af
总结
以上是生活随笔为你收集整理的剑指Offer:剪绳子(动态规划、贪婪算法)的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 剑指Offer:包含main函数的栈(借
- 下一篇: 在SecureCRT中,hbase sh