剪绳子

剪绳子

http://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8

题目的主要信息:
  • 把一根长度为nn的绳子分成mm段,每段长度都是整数
  • 求每段长度乘积的最大值
举一反三:

学习完本题的思路你可以解决如下题目:

JZ83. 剪绳子(进阶版)

JZ71. 跳台阶扩展问题

JZ42. 连续子数组的最大和

方法一:动态规划(推荐使用)

知识点:动态规划

动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果。

思路:

一旦分出一段长度为1的小段,只会减少总长度,还不能增加乘积,因此长度为2的绳子不分比分开的乘积大,长度为3的绳子不分比分开的乘积大,长度为4的绳子分成2*2比较大。前面的我们都可以通过这样递推得到,后面的呢?

同样递推!如果我有一个长度为nn的绳子,我们要怎么确定其分出最大的乘积,我们可以尝试其中一段不可分的为jj,那么如果另一段njn-j最大乘积已知,我们可以遍历所有jj找到这个最大乘积。因此用dp[i]dp[i]表示长度为i的绳子可以被剪出来的最大乘积,那么后续遍历每个jj的时候,我们取最大dp[i]=max(dp[i],jdp[ij])dp[i] = max(dp[i], j * dp[i - j])就好了。

//可以被分成两份
for(int j = 1; j < i; j++)
    //取最大值
    dp[i] = max(dp[i], j * dp[i - j]);

具体做法:

  • step 1:检查当number不超过3的时候直接计算。
  • step 2:用dp数组表示长度为i的绳子可以被剪出来的最大乘积,初始化前面4个容易推断的。
  • step 3:遍历每个长度,对于每个长度的最大乘积,可以遍历从1到ii的每个固定一段,按照上述公式求的最大值。
  • step 4:最后数组最后一位就是答案。

图示:

alt

Java实现代码:

import java.util.*;
public class Solution {
    public int cutRope(int target) {
        //不超过3直接计算
        if(target <= 3) 
            return target- 1;
        //dp[i]表示长度为i的绳子可以被剪出来的最大乘积
        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++)
            //可以被分成两份
            for(int j = 1; j < i; j++)
                //取最大值
                dp[i] = Math.max(dp[i], j * dp[i - j]);
        return dp[target];
    }
}

C++实现代码:

class Solution {
public:
    int cutRope(int number) {
        //不超过3直接计算
        if(number <= 3) 
            return number - 1;
        //dp[i]表示长度为i的绳子可以被剪出来的最大乘积
        vector<int> dp(number + 1, 0);
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        dp[4] = 4;
        //遍历后续每一个长度
        for(int i = 5; i <= number; i++)
            //可以被分成两份
            for(int j = 1; j < i; j++)
                //取最大值
                dp[i] = max(dp[i], j * dp[i - j]);
        return dp[number];
    }
};

Python实现代码:

class Solution:
    def cutRope(self , number: int) -> int:
        #不超过3直接计算
        if number <= 3: 
            return number - 1
        #dp[i]表示长度为i的绳子可以被剪出来的最大乘积
        dp = [0 for i in range(number + 1)]
        dp[1] = 1
        dp[2] = 2
        dp[3] = 3
        dp[4] = 4
        #遍历后续每一个长度
        for i in range(5, number + 1):
            #可以被分成各种长度的两份
            for j in range(1, i):
                #取最大值
                dp[i] = max(dp[i], j * dp[i - j])
        return dp[number]

复杂度分析:

  • 时间复杂度:O(n2)O(n^2),两层遍历
  • 空间复杂度:O(n)O(n),辅助数组dp的空间
方法二:贪心(扩展思路)

知识点:贪心

贪心思想属于动态规划思想中的一种,其基本原理是找出整体当中给的每个局部子结构的最优解,并且最终将所有的这些局部最优解结合起来形成整体上的一个最优解。

思路:

根据均值不等式,有:n1+n2+...+nmmn1n2...nmm\frac{n_1+n_2+...+n_m}{m}\geq \sqrt[m]{n_1n_2...n_m},等号当且仅当n1=n2=n3=...nmn_1=n_2=n_3=...n_m时成立,因为加法部分和是固定的绳子总长度,因此要使乘积最大,应该以相等的长度等分成多段。

如果将绳子按照每段长度为xx等分成mm段,则n=mxn=mx,乘积为xmx^m,因为有xm=xnx=(x1x)nx^m=x^{\frac{n}{x}}=(x^{\frac{1}{x}})^n因此当x1xx^{\frac{1}{x}}取最大值时取最大值。

y=x1xy=x^{\frac{1}{x}},即求这个函数的极值即可直到绳子等分成长度为多少可以使乘积最大。根据取对数、求导、求极值等一系列数学操作,得驻点为x0=ex_0=e,即极大值需要将绳子分成每段e,但是绳子长度只能是整数,靠近e的只有2 和3,二者分别代入公式,发现当x=3x=3是,乘积达到最大。

因此后续,使用贪心思想,不断将绳子分成每段长度为3即可,不足3的可以考虑,如果最后剩余的是2,直接乘上,如果最后剩余的是1,则取出一个3组成4分成长度为2的两段,因为22>132*2>1*3

具体做法:

  • step 1:按照上述思路,不超过3的直接计算
  • step 2:超过3的不断累乘3,然后number不断减去3,直到最后不超过4。
  • step 3:最后乘上剩余的数字。

Java实现代码:

public class Solution {
    public int cutRope(int target) {
        //不超过3直接计算
        if(target <= 3) 
            return target - 1;
        int res = 1;
        while(target > 4){
            //连续乘3
            res *= 3; 
            target -= 3; 
        }
        return res * target;   
    }
}

C++实现代码:

class Solution {
public:
    int cutRope(int number) {
        //不超过3直接计算
        if(number <= 3) 
            return number - 1;
        int res = 1;
        while(number > 4){
            //连续乘3
            res *= 3; 
            number -= 3; 
        }
        return res * number;
    }
};

Python实现代码:

class Solution:
    def cutRope(self , number: int) -> int:
        #不超过3直接计算
        if number <= 3: 
            return number - 1
        res = 1
        while number > 4:
            #连续乘3
            res *= 3 
            number -= 3 
        return res * number

复杂度分析:

  • 时间复杂度:O(n)O(n),其中nn为绳子的长度,最坏需要计算n/3n/3
  • 空间复杂度:O(1)O(1),常数级变量,无额外辅助空间
全部评论
递归法初始条件错了,m>1,输入n=3或n=2,back_track(n) = n-1
9 回复 分享
发布于 2020-06-07 10:39
方法一的暴力递归解法,无法AC
3 回复 分享
发布于 2020-07-10 20:39
既然说back_track(n); 含义是:求长度为n的数,最后分段后的最大乘积,那为什么当n<=4的时候,back_track(n),return n呢?明明n=2时候,安装含义应该返回1,n=3返回2。当然这样写的话是错误的,可以按照暴力法的思路来不就应该是这样吗? 动态规划也是,dp[n],当n<=4的时候值都为n,按照dp每个状态的定义,也应该是之前说的,2,3啊。。
3 回复 分享
发布于 2020-07-14 23:48
方法二中numberm应该是number,还有mark[n]应该改为mark[n-1],因为下标是从0开始的
1 回复 分享
发布于 2020-07-02 23:13
请问为什么方法2的递归复杂度为O(n^2)?
1 回复 分享
发布于 2021-01-03 12:27
方法二中存在一点小笔误,修改一下就可以了,修改一: if(mark[n-1] != -1){ return mark[n-1]; } 修改二: int ret = 0; for (int i = 1; i < n; ++i) { ret = max(ret, i * back_track(n - i, mark)); } 修改三: mark[n-1] = ret; return ret; 修改四:return back_track(number, mark);
1 回复 分享
发布于 2021-02-26 14:32
方法三的for(int j=1;j<=i;++j)可以优化成for(int j=1;j<=i/2;++j) 比如2*dp[3]跟3*dp[2]是一样的
1 回复 分享
发布于 2022-03-18 09:50
动态规划的思路:在n>=7的时候只需要判断dp[i-2]*2与dp[i-3]*3的较大值作为dp[i]的值即可,因为理想的乘积组合一定是若干个2与3的乘积(4可以看做是2*2),可以将时间复杂度优化到O(n),空间复杂度优化到O(1)
1 回复 分享
发布于 2022-05-27 19:25
动态规划的方法,可以将第二个循环判断改为j<(i/2)+1,可以减少重复判断次数
点赞 回复 分享
发布于 2020-07-19 10:24
想问一下转移方程f[i] = max(f[i], j * f[i - j])中的j不是应该写成f[j]吗,当j比f[j]小的时候,这样写结果不是更大吗?
5 回复 分享
发布于 2020-10-04 17:15
牛客网给的答案建议再严谨一些,看这几次答案搞得我渐渐不相信你们的官方答案了,要不是网友们的解答我还真还蒙在鼓里呢。。。
5 回复 分享
发布于 2020-11-24 16:53
为什么前4个数f[i]=i啊,不应该是f[1]=0,f[2]=1,f[3]=2,f[4]=4吗?
3 回复 分享
发布于 2020-07-17 16:42
不是,题目要求m>1,意思就是起码得剪一刀,长度为1的时候没法剪,长度为2时,剪成1、1,乘积为1,长度3,乘积2,这初始条件都不对,为什么最后结果是对的啊?
1 回复 分享
发布于 2022-12-22 00:04 英国
想问一下,动态规划方法中 f[i] = max(f[i], j * f[i - j]);f[i]未知,怎么和j * f[i - j]比较大小,如何选出最大值?感谢回答。
点赞 回复 分享
发布于 2020-07-02 22:00
方法三中第二层循环“for (int j = 1; j < i; ++j)”可以将j<i改成j><5吧,因为f[5]=6,大于5了</i改成j>
点赞 回复 分享
发布于 2020-07-08 21:18
方法2错了吧,back_track函数里面两个参数,然后for循环里面back_track参数只有一个,求翻牌?
点赞 回复 分享
发布于 2020-10-23 10:56
动态规划代码第二个for循环里,f[i] = max(f[i], j * f[i - j]); 为什么是j不是f[j]? 为什么我用f[j]也是对的?
点赞 回复 分享
发布于 2020-11-19 21:55
原答案思路是正确的,但不能ac,附可以ac的代码,主要在mark数组的边界上 class Solution { public: int back_track(int n, vector<int>& mark) { // n <= 4, 表明不分,长度是最大的 if (n <= 4) { return n; } if(mark[n] != -1){ return mark[n]; } int ret = 0; for (int i = 1; i < 5; ++i) { ret = max(ret, i * back_track(n - i, mark)); } return mark[n]=ret; } int cutRope(int number) { // number = 2 和 3 时,分 2 段和分 1 段的结果是不一样的,所以需要特判一下 if (number == 2) { return 1; } else if (number == 3) { return 2; } vector<int> mark(number+1,-1); return back_track(number, mark); } };</int></int>
点赞 回复 分享
发布于 2020-12-18 13:23
方法三中的内部循环条件应该为“for (int j = 1; j <= i / 2; j++)”,原条件“for (int j = 1; j <=i; j++)”会导致重复运算,即时间复杂度:O(n*log2 n),空间复杂度:O(n)
点赞 回复 分享
发布于 2021-01-20 23:31
这个精华有点牵强了。 硬套公式还有笔误。
点赞 回复 分享
发布于 2021-04-02 11:22

相关推荐

做人要有梦想dji:最新工位查看图片
点赞 评论 收藏
分享
01-24 08:13
已编辑
合肥工业大学 Java
程序员牛肉:没啥问题。标准的流水线简历,但是学历好一点,所以应该是有约面的机会的。 这段时间可以考虑把自己的两个项目彻底的理一理。争取能够讲清楚每一个功能点
点赞 评论 收藏
分享
评论
131
16
分享

创作者周榜

更多
牛客网
牛客企业服务