剑指Offer对答如流系列 - 剪绳子

面试题13:剪绳子

题目描述

 给你一根长度为n绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1)。每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]*k[1]*…*k[m]可能的最大乘积是多少?

例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。

问题分析

(1)思路一:动态规划

遇到问题,先分析问题,由分析的结果确定所运用的算法。

一般而言,我们都会在纸上动笔画画,罗列一些基本的情况。

题中说n>1并且m>1,那么

  • 当n=2时,最大乘积只能为1,只有一种情况1*1;
  • 当n=3时,最大乘积只能为2,有两种情况,1*2,1*1*1;
  • 当n=4时,可以分为如下几种情况:1*1*1*1,1*2*1,1*3,2*2,最大乘积为4;
  • 当n=x时,剪第一刀的时候,我们有x-1种可能的选择,也就是剪出的一段绳子的长度为1、2、...、x-1

参考n= x的情况,如果把长度n绳子的最大乘积记为f(n),则有:f(n)=max(f(i)*f(n-i)),0<i<n。

当你分析到这个的时候,思路就得到了一层的突破:从下往上推,先算小的问题,再算大的问题,大的问题通过寻找小问题的最优组合得到。这个不就是动态规划吗?

动态规划算法的定义是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

既然写到了动态规划,那我就将我所知道的都告诉你:

关于动态规划算法定义的理解

  • 关于拆分问题 : 就是根据问题的性质把问题划分。(我们上述基本情况的讨论,最后得出n = x的普遍情况,走的就是这一步)
  • 关于定义问题状态和状态之间的关系: 也就是上面拆分的步骤之间的关系,用一种量化的形式表现出来,这种形式一般可以说是递推式(我们最后得出的f(n)=max(f(i)*f(n-i)),0<i<n 就是走的这一步)

至于算法思想的理解,我们解答中探讨

(2)思路二:贪心

这个涉及到一个数学模型,原本的定义非常宏大和复杂,这里不多赘述,我们这里只是涉及到一丁点的应用,你有个印象即可。

我们之前也讨论了,刚才的递推式f(n)=max(f(i)*f(n-i)),0<i<n。 是以n =2、n =3 为基石的。

对于任意一个大于或者等于5的数字,都可分解为加数 2 和 3的和。(这个小学数学就能证明,不证明了哈)

递推式涉及到乘积,很容易注意且能证明3(length-3)>2(length-2),因此要想乘积最大,尽可能分出更多的加数3

除了上述所说的,我们还有一个特殊的数字就是4,当length=4的时候,13<22=4,所以length=4的时候不用再分。

局部最优能保证整体最优,贪心算法写起来要比动态规划简单多了。

问题解答

(1)动态规划算法的实现

     /**
     * @param length 输入的绳子的长度
     * @return 乘积的最大值
     * 动态规划的算法思想:
     * 动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。
     * 在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
     */
    public int maxProductAfterCutting(int length) {
        // 递推式: f(n)=max(f(i)*f(n-i)),0<i<n
        if (length <= 1)
            return 0;
        if (length == 2)
            return 1;
        if (length == 3)
            return 2;
        // 存储最优解
        int[] product = new int[length + 1];
        // 下面几个不是乘积,因为其本身长度比乘积大
        product[0] = 0;
        product[1] = 1;
        product[2] = 2;
        product[3] = 3;

        // 开始从下到上计算长度为i绳子的最大乘积值product[i]
        for (int n = 4; n <= length; n++) {
            int max = 0;
            // 算不同子长度的乘积,找出最大的乘积
            // 这就是算法中 找到 最优解 步骤
            for (int i = 1; i <= n / 2; i++) {
                if (max < product[i] * product[n - i])
                    max = product[i] * product[n - i];
            }
            // 把最优解保存下来(为了后面的使用的最优解,为局部最优解)
            product[n] = max;
        }
        return product[length];
    }

(2)贪心算法的实现

  public int maxProductAfterCutting(int length) {
        if (length <= 1)
            return 0;
        if (length == 2)
            return 1;
        if (length == 3)
            return 2;
        // 确定分解成一段段长度为3的绳子的个数
        int timesOfThree = length / 3;
        if (length - timesOfThree * 3 == 1) {
            timesOfThree--;
        }
        int timesOfTwo = (length - timesOfThree * 3) / 2;
        return (int) (Math.pow(3, timesOfThree) * Math.pow(2, timesOfTwo));
    }

原文地址:https://www.cnblogs.com/JefferyChenXiao/p/12246300.html

时间: 2024-10-03 20:01:00

剑指Offer对答如流系列 - 剪绳子的相关文章

剑指Offer对答如流系列 - 礼物的最大价值

面试题47:礼物的最大价值 题目描述 在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0).你可以从棋盘的左上角开始拿格子里的礼物,并每次向左或者向下移动一格直到到达棋盘的右下角.给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物? 比如下面的棋盘中,如果按照红色数字的路线走可以拿到最大价值为53的礼物 问题分析 动态规划:定义f(i,j)为到达(i,j)位置格子时能拿到的礼物总和的最大值,则有:f(i,j)=max{f(i-1,j),f(i,j-1)}+va

剑指Offer对答如流系列 - 序列化二叉树

面试题37:序列化二叉树 题目描述 请实现两个函数,分别用来序列化和反序列化二叉树. 树的结构定义如下: public class Node { int val = 0; Node left = null; Node right = null; public Node(int val) { this.val = val; } } 问题分析 一般情况下,需要采用前/后序遍历和中序遍历才能确定一个二叉树,具体的内容我们之前探讨过 剑指Offer对答如流系列 - 重建二叉树 但是采用这种方式进行序列化

剑指Offer对答如流系列 - 把数组排成最小的数

面试题45:把数组排成最小的数 题目描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3, 32, 321},则打印出这3个数字能排成的最小数字321323. 问题分析 之前我们做过字符全排列的习题 剑指Offer对答如流系列 - 字符串的排列,但是将算法思想应用到这一题的话,效果不好,求出所有的组合,再计算出组合的最小值,这效率该多低啊. 我们还要进一步探究,看看有没有不错的规律,供我们使用. 因为数字拼接后的长度一样,拼接后的结果

剑指Offer对答如流系列 - 从上往下打印二叉树

面试题32:从上往下打印二叉树 题目描述 树的结构定义如下: public class Node{ int e; Node left; Node right; Node(int x) { e = x; } } (一)不分行从上到下打印二叉树 从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印. 比如下面二叉树,输出顺序为 8 6 10 5 7 9 11 (二)分行从上到下打印二叉树 从上到下按层打印二叉树,同一层的结点按从左到右的顺序打印,每一层打印到一行. 比如下面二叉树,输出

剑指Offer对答如流系列 - 数组中数字出现的次数

面试题56:数组中数字出现的次数 题目描述 问题(1)数组中只出现一次的两个数字 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度是O(n),空间复杂度是O(1). 问题(2)数组中唯一只出现一次的数字 在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次.请找出那个只出现一次的数字. 问题分析 问题(1)分析 在这篇文章剑指Offer对答如流系列 - 二进制中 1 的个数中,我们详细探讨了位运算,其中有重要的一条:两个相同的数异

剑指Offer对答如流系列 - 求1+2+…+n

面试题64:求1+2+-+n 题目描述 求1+2+-+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C). 问题分析 有了那么多限制,剩下的我们可以选择 单目运算符:++和--,双目运算符:+,-,移位运算符<>,关系运算符>,<等 逻辑运算符&&,||,&,|,^,赋值= 既然是一个等差数列,和为(n+1)*n/2 我们之前详细探讨了位运算剑指Offer对答如流系列 - 二进制中 1 的个

剑指Offer对答如流系列 - 不用加减乘除做加法

面试题65:不用加减乘除做加法 题目描述 写一个函数,求两个整数之和,要求在函数体内不得使用+.-.×.÷四则运算符号. 问题分析 我们之前详细探讨了位运算 剑指Offer对答如流系列 - 二进制中 1 的个数,已经非常非常详细了. 这道题仅仅是让做加法,我们除此之外还是做了乘除与减法. 记不清的朋友可以回头看看. 这里象征性地做一次解答吧 问题解答 public int add(int num1,int num2) { while(num2!=0){ int sum=num1^num2; in

剑指Offer对答如流系列 - 从1到n整数中1出现的次数

面试题43:从1到n整数中1出现的次数 题目描述 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数. 例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次. 问题分析 最容易想到的思路是通过对10求余数判断整数的个位数字是不是1.代码书写也很简单,但是如果输入的整数n比较大的时候,会有大量的运算. 1是由于数字递增出现的,而十进制影响这种出现的周期性.这本身肯定存在规律,重要的是耐心寻找,不要妄想一次性就找出来,下面的规律要比<剑指Offer>

剑指Offer对答如流系列 - 二进制中 1 的个数

面试题14:二进制中 1 的个数 题目描述 请实现一个函数,输入一个整数,输出该数二进制表示中1的个数.例如把9表示成二进制是1001,有2位是1.因此如果输入9,该函数输出2. 问题分析与解决 这道面试题归属于 <剑指Offer>位运算章节.遇到二进制相关的问题,很容易想到位运算,虽然种类不多(与.或.异或.左移.右移),但是搞起来是千变万化的.待会再和你侃一些骚操作,我们先看这道题. (一)思路一 "与运算"有一个性质:通过与对应位上为1,其余位为0的数进行与运算,可以