【剑指offer第2版-第15题-JAVA】

剪绳子

题目:给你一根长度为n的绳子,请把绳子剪成m段(m,n都是整数,并且m>1,n>1),每段绳子的长度记为k[0],k[1],···,k[m]。
请问k[0]×k[1]×···×k[m]可能的最大乘积是多少?例如:当绳子长度为8时,我们把它剪成长度233的三段,此时得到的最大乘积是18

我们有两种不同的方法解决这个问题,先用常规的O(n²)时间和O(n)空间的动态规划的思路,接着用只需要O(1)时间和空间的贪婪算法来分析解决这个问题。

动态规划

首先定义函数f(n)为把长度为n的绳子剪成若干段后各段长度的乘积最大值,在剪第1刀的时候,我们有n-1种长度可能的选择,也就是剪出来的第一段绳子的可能长度分别为1,2,···,n-1。因此f(n)=Max(f(i)*f(n-i)),其中0<i<n。这是一个从上至下的递归公式,中间有大量不必要的计算,更好的办法是从下而上的方式,想不清楚的时候可以画图,或者穷举一些简单的数据作为辅助如下表:

绳子长度n切割次数I(0~n)长度乘积穷举最大乘积
211x11
311x22
21x1x1
411x3,2x24
21x1x2
31x1x1x1
511x4,2x36
21x1x3,1x2x2
31x1x1x2
41x1x1x1x1
每段最小的长度只能为1,长度为n的话,最多可以剪n-1次,由表格分析,切的次数越多,则每段长度越小,则乘积越小,理论上切至n/2次之后,乘积越来越小。上代码:
    public static int maxCordCut(int length) {
        //长度小于2,没法下刀
        if (length < 2) {
            return 0;
        }
        //长度等于2,只能切1刀,每段最小长度为1
        if (length == 2) {
            return 1;
        }
        //长度等于3,可以切2刀,乘积为2,切3刀,每段为1,乘积还是1
        if (length == 3) {
            return 2;
        }
        //穷举下长度为4,每段绳子的可能刀长度值长度值
        int ans[] = new int[length + 1];
        ans[0] = 0;
        ans[1] = 1;
        ans[2] = 2;
        ans[3] = 3;
        int max = 0;
        //长度2,3已知,长度从4开始算
        for (int i = 4; i <= length; ++i) {
            //切多少刀
            for (int j = 1; j <= i / 2; ++j) {
                //计算乘积,比较大小
                int result = ans[j] * ans[i - j];
                if (max < result) {
                    max = result;
                }
                ans[i] = max;
            }
        }
        max = ans[length];
        return max;
    }

贪婪算法

如果我们按照如下的策略剪绳子,则得到的各段绳子的长度将最大:
当n≥5时,我们尽可能的多剪长度为3的绳子,当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子,这种思路的数学逻辑是这样的,当n≥5时,我们可以证明2x(n-2)>n并且3x(n-3)>n 和3x(n-3)>n≥2x(n-2);因此我们应该尽可能的多剪长度为3的绳子,那么当绳子的长度为4呢?在长度为4的绳子上剪一刀,那么只有两种可能1x3,2x2;且2x2>1x3。上代码:

 public static double maxCordCutNew(int length) {
        //长度小于2,没法下刀
        if (length < 2) {
            return 0;
        }
        //长度等于2,只能切1刀,每段最小长度为1
        if (length == 2) {
            return 1;
        }
        //长度等于3,可以切2刀,乘积为2,切3刀,每段为1,乘积还是1
        if (length == 3) {
            return 2;
        }
        //尽可能多切长度为3的绳子,求可以切几刀
        double timesOfThree = length / 3;
        //当绳子最后的长度为4时,不能再去剪去长度为3的绳子段,更好的方法是剪成绳子长度为2的两段,因为2x2>3x1
        if (length - timesOfThree * 3 == 1) {
            timesOfThree = 1;
        }
        //尽可能多切长度为2的绳子,减去已切(长度为3*切长度为3的次数),求可以切几刀
        double timesOfTwo = (length - timesOfThree * 3) / 2;
        return Math.pow(3.0, timesOfThree) * Math.pow(2.0, timesOfTwo);
    }

个人理解

1.和斐波那契数列的从下而上的递归方式相同,但又不太相像。
2.可以画图穷举一些数据,来辅助自己抽象化逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值