算法题:剑指 Offer 14- I. 剪绳子(题目+思路+代码+注释)时空 O(1) O(1) 0ms击败100%、74%用户(数学推导运算法和状态转移方程的记忆法两种解法)

在这里插入图片描述

题目

剑指 Offer 14- I. 剪绳子

给你一根长度为 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
注意:本题与主站 343 题相同:https://leetcode-cn.com/problems/integer-break/

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jian-sheng-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

我们都知道,假设给你一根绳子,让你围成一个四边形,那么一定是围成正方形的面积最大,其实也就是当一个长度为n的绳子剪断成x份的时候一定是让每一段长度相等的时候结果是最大的。那么每段的长度是 n/x,有x段,那么结果是 (n/x)^x,求最大值,其实是不好求的,反正我是不会,那就观察吧
2 拆成1 1
3 拆成1 2
4拆成 2 2
5拆成2 3
6拆成3 3
7拆成3 4
8拆成3 3 2
9拆成3 3 3
10拆3 3 4
11拆3 3 3 2
12拆3 3 3 3
我们已经从2份拆到了4分,看看规律,其实我们发现,似乎大于等于3以后再也没有了1,而且后面的全部都是拆成质数 2 3 另外再加一个4(3+1>3*1),因此我们其实可以知道,而2和3优先选谁呢,例如6我们可以拆成3个2,可以拆成2个3,而拆成3的乘积是最大的,因此优先选3,拆完了所有3之后其实只可能剩下 0 1 2这3中情况
剩下0 全部拆成了3
剩下1 最后一个3加上剩下的1组成4是最大的
剩下2 最后一个2乘以前面的是最大的
因此我们可以对于小于4的值直接返回n-1,大于4的呢,我们就看对3取余后剩下多少,然后根据不同的剩下多少直接算出最大乘积
if(n < 4)
直接返回n -1
else
n%3 == 0: 最大值为:Math.pow(3,n/3);
n%3== 1:最大值为:Math.pow(3, ((int)(n/3))-1)*4
n%3== 2:最大值为:Math.pow(3,(int)(n/3)))*2
然后就可以写代码了
我看其他题解的时候有人说 (n/x)^x的最大极限时 x = e = 2.7…自然系数,最靠近的是3,因此优先取3,我也没有算也算不出来这个表达式的最大值。。。那就找规律呗,好好的一个算法题成了个数学题,因此能够直接算出答案。当然我也看到了有用记忆法的,有用动态规划的,那我也来试试记忆法解,当然效率上没办法比过这个直接算出结果的。
我们将整个切割的过程提炼为一个状态转移方程,假设长度为n,正在做最后一次切割
最后一次可能切1 2 3三种情况,取最大值即可作为本次切割能做出的最大值选择,设求长度为n的绳子可切割出的最大子段长度乘积为f(n)则最后状态转移方程f(n)可表示为:
f(n) = max( f(n-1) , f(n-2)*2 , f(n-3)*3 )

代码

直接计算法

public int cuttingRope(int n) {
        if(n < 4){
            return n-1;
        }
        switch (n%3){
            case 0:{
                return (int)Math.pow(3,n/3);
            }
            case 1:{
                return (int)Math.pow(3,(((int)n/3)-1))*4;
            }
            case 2:{
                return (int)Math.pow(3,((int)n/3))*2;
            }
        }
        return 0;
    }

记忆法

public int cuttingRope(int n) {
        if(n < 4){
            return n-1;
        }
        int[] dp = new int[n+1];
        dp[2] = 2;//因为n>4则2是不需要再切断的,可以取2而不是1
        dp[3] = 3;//3一定不要算成2,因为n必定大于4,能够满足拆成2段以上所以这个3不需要拆成1 2,所以这个地方是要取3!
        dp[4] = 4;
        for (int i = 5; i <= n;i++){
            dp[i] = Math.max(dp[i-1],Math.max(dp[i-2]*2,dp[i-3]*3));
        }
        return dp[n];
    }
posted @ 2021-08-28 17:12  HumorChen99  阅读(18)  评论(0编辑  收藏  举报  来源