将长度为n的绳子分为m段求各段乘积的最大值

1 将长度为n的绳子分为m段求各段乘积的最大值

1.1 题目描述

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

1.2 动态规划法解题

动态规划法:
看题目就知道这玩意要用动态规划实现,可是就是不知道状态转移方程如何推导出来,费了九牛二虎之力才把别人的讲解看懂,这思维根本不在一个层面啊,看来还是太菜,路漫漫兮。

先来看下动态规划求解问题的四个特征:
①求一个问题的最优解;
②整体的问题的最优解是依赖于各个子问题的最优解;
③小问题之间还有相互重叠的更小的子问题;
④从上往下分析问题,从下往上求解问题;

如下开始对题目进行分析:
有一段长度为n的绳子,我们现在要剪第一刀,我可以选择下第一刀的地方有1 ~ n-1这些地方;比如长度为10的绳子,我第一刀可以在1~9这些地方下刀,共9种方式。

第一刀下去后,绳子分成两部分,假设在i处下刀,绳子两部分就分别为:[0 ~ i]与[i ~ n],长度分为表示为i与n-i;那么找出第一刀最合适的位置,其实就是找i在哪下刀,可以使得[0 ~ i]与[i~n]的乘积最大,函数表示为:f(n)=max(f(i)×f(n−i))。

那么如何判断i处切最大呢?这个时候,我们就要知道,[0~i]这个长度的绳子,任意方式切,最大的乘积是多少;假如说,当我们要切一个长度为10的绳子:切成1和9与4和6,两种方式,哪个乘积更大?

回答:不光要考虑第一刀后两个绳子的大小,还要考虑到9、4、6这三种情况,因为第一刀切出的绳子长度是否可以再切第二刀,使它有更大的乘积,比如将9再切成 3×3×3,6切成 4×2,哪个更大?

这种情况下,我们可以发现,无论再怎么切,一定是越切越短,那么我们是否可以将小于给定长度的绳子的每一个长度的最大乘积都求出来?

即:长度为10的绳子,我们就计算出:长度1~9这9种长度的绳子,每种长度的最大乘积是多少。 要求长度9的绳子的最大乘积,我们要知道1 ~ 8各个长度的最大乘积,要知道长度8的最大乘积,就要知道1 ~ 7长度的各个最大乘积,以此类推。

代码分析如下:
f(n)定义为将长度为n的绳子分成若干段后的各段长度的最大乘积(最优解),在剪第一刀时有n-1种剪法,可选择在0 < i < n处下刀。在i处下刀,分成长度为i的左半绳子和长度为n-i的右半绳子,对于这两根绳子,定义最优解为f(i)和f(n-i),于是f(n) = max(f(i) * f(n-i)),即求出各种相乘可能中的最大值就是f(n)的最优解。就这样从上到下的分下去,但是问题的解决从下到上。即先求出f(2)、f(3)的最优解,然后根据这两个值求出f(4)、f(5)…直到f(n)。
f(2) = 1,因为只能分成两半;f(3) = 2,因为分成两段21 大于分成三段的11*1。

    public static int maxProductAfterCutting(int length){
        //长度小于2 无法分割
        if(length<2)
            return 0;
        //长度等于2 一分为二 1*1
        if(length==2)
            return 1;
        //长度等于3 最大为1*2=2
        if(length==3)
            return 2;
        //定义一个存放>=4 长度的数组 ,对>=4长度的最大的乘积进行临时存储
        int[] products=new int[length+1];
        //以下的前三个数组存放的不是最大值,而是长度值
        products[1]=1;
        products[2]=2;
        products[3]=3;
        for(int i=4;i<=length;i++) {
            int maxModify=0;
            for(int j=1;j<=i/2;j++) {
                int product=products[j]*products[i-j];
                if(product>maxModify)
                    maxModify=product;
            }    
            //得到f(i)的最优解
            products[i]=maxModify;
        }    
        //返回发f(n)
        return products[length];
    }
    

也可使用递归的方式进行求解:

int MaxProductCutting(int length)
{
	if (length < 4)
	{
		return length;
	}

	int max = 0;
	for (int i = 1; i <= length / 2; i++)
	{
		int val = MaxProductCutting(i) * MaxProductCutting(length - i);

		if (val > max)
		{
			max = val;
		}
	}

	return max;
}

int MaxProductAfterCutting1(int length)
{
	if (length <= 1) return 0;
	if (length == 2) return 1;
	if (length == 3) return 2;

	return MaxProductCutting(length);
}

1.3 贪心法求解

贪婪法,不断分出长度为3的绳子,如果最后只剩下长度为1的绳子,退一步,将得到长度为4的绳子,然后将这长度为4的绳子分成22(这样分是因为22大于原来的31)。因此n = 4时不能分出长度为3的绳子,而n = 2,n = 3可直接返回。当n >=5时候,满足n >=5这个不等式的有2(n-2) > n以及3*(n-3) > n 。注意到2+n-2 = 3+n-3 = n,也就是说分出的两个相乘的数要满足和为n,且同样的n,3*(n-3)的值更大,这就是为什么要不断分出长度为3的绳子的原因。

代码如下:

public static int maxProductAfterCutting2(int length) {
        // 长度为1时不满足题意,返回0
        if (length < 2) {
            return 0;
        }
        // f(2)
        if (length == 2) {
            return 1;
        }
        // f(3)
        if (length == 3) {
            return 2;
        }    
       //     统计能分出多少段长度为3的绳子
        int timesOf3 = length / 3;
        // 如果最有只剩下长度为1的绳子,需要退一步,得到长度为4的绳子,重新分成2*2的
        if (length - timesOf3 * 3 == 1) {
            timesOf3--;
        }
        // 到这步length - timesOf3 * 3的值只可能是0,2,4,所以timesOf2只可能是0, 1, 2
        int timesOf2 = (length - timesOf3 * 3) / 2;
        return (int) Math.pow(3, timesOf3) * (int) Math.pow(2, timesOf2);    
    }

参考资料:

  1. https://blog.csdn.net/u012429555/article/details/83184146
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值