剪绳子(动态规划、贪心算法)

一、前言

《剑指Offer》中题14

 

二、题目

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

 

三、思路

第一种解法:递归自顶向下解法

当第一次看到这个问题的时候第一反应的什么呢?有点蒙,再细细想想这个问题。将一根绳子分为m段其乘积最大,m段必定有两部分组成定为m1,m2,要想m段乘积最大,就需要保证m1段中的分割乘积是最大,同理m2也需一样。先看下面图,n第一次分割之后如下:

备注,第一行和下面组合不是两个值直接相乘,而是拆分之后最大的值相乘,例如 m1 长度为 6, m2 长度 n-6,m1分割最大乘积是9(3*3)

最大的结果肯定为其中一个组合,即:f(n) = max(f(i) X f(n-i));同理 f(i),f(n-i)又可以往下切割,直到最小单位位置,最小切割单位为 f(1) = 1,f(2) = 2, f(3) = 3。

分析完成脑海立马想到的是递归解法,递归简单易懂。

第二种解法:递推自下而上解法

递归很好理解,但递归重复了大量计算,随着n值增大,效率呈指数下降。递归是自顶向下,那有没有自下而上的解法呢?

回顾一下斐波那契数列第二种解法,它是每次计算f(k),f(k+1)的值,通过f(k),f(k+1)可以求出f(k+2),通过f(k+1),f(k+2) 可以求出f(k+2),一直到f(n)。

同理我们要求f(k) = max(f(i) X f(k-i)),我们需要提前记录f(i),f(k-i)最大的值,循环遍历计算之后就能求出f(k)最大值,同理以此往下计算到f(n)为止就可以了

第三种解法:贪心解法(更像是数学推导的结果)

当 n >= 5 时,可以证明:2(n-2) > n && 3(n-3) > n。也就是说当绳子剩下的长度大于或者等于5的时候,我们可以将绳子剪成长度为 3 或 2。另外,当 n >= 5时,3(n-3) >= 2(n-2),因此我们应该将可能多的剪成长度为3绳子段。

需要注意的是 第一种和第二种解法属动态规划,第三属贪心算法。后续会详细讲解。

 

四、注意事项

多领悟里面的思想。

 

五、编码实现

#include <iostream>
#include <cmath>

// ====================动态规划====================

// 解法一: 递归自顶向下解法
int maxProductSubset(int length)
{
	if (length <= 0)
	{
         return 0;
	}

	int maxProductArr[] = { 0, 1, 2, 3, 4 };
	if (length < sizeof(maxProductArr) / sizeof(maxProductArr[0]))
	{
        return maxProductArr[length];
	}

	int maxProduct = 0;
	for (int i = 1; i <= length / 2; ++i)
	{
        int maxProductT = maxProductSubset(i) * maxProductSubset(length - i);
        maxProduct = maxProduct > maxProductT ? maxProduct : maxProductT;
	}

	return maxProduct;
}

int maxProductAfterCutting_solution1(int length)
{
	if (length <=0)
	{
         return 0;
	}

	int maxProductArr[] = { 0, 0, 1, 2, 4 };
	if (length < sizeof(maxProductArr) / sizeof(maxProductArr[0]))
	{
        return maxProductArr[length];
	}

	int maxNum = 0;
	for (int i = 1; i <= length / 2; ++i)
	{
        int product = maxProductSubset(i) * maxProductSubset(length - i);
        maxNum = maxNum > product ? maxNum : product;
	}

	return maxNum;
}

// 解法二: 递推自下而上解法
int maxProductAfterCutting_solution2(int length)
{
    if(length < 2)
        return 0;
    if(length == 2)
        return 1;
    if(length == 3)
        return 2;

    int* products = new int[length + 1];
    products[0] = 0;
    products[1] = 1;
    products[2] = 2;
    products[3] = 3;

    int max = 0;
    for(int i = 4; i <= length; ++i)
    {
        max = 0;
        for(int j = 1; j <= i / 2; ++j)
        {
            int product = products[j] * products[i - j];
            if(max < product)
                max = product;
        }
        
        products[i] = max;
    }

    max = products[length];
    delete[] products;

    return max;
}

// ====================贪婪算法====================
// 解法三: 贪心算法
int maxProductAfterCutting_solution3(int length)
{
    if(length < 2)
        return 0;
    if(length == 2)
        return 1;
    if(length == 3)
        return 2;

    // 尽可能多地减去长度为3的绳子段
    int timesOf3 = length / 3;

    // 当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子段。
    // 此时更好的方法是把绳子剪成长度为2的两段,因为2*2 > 3*1。
    if(length - timesOf3 * 3 == 1)
        timesOf3 -= 1;

    int timesOf2 = (length - timesOf3 * 3) / 2;

    return (int) (pow(3, timesOf3)) * (int) (pow(2, timesOf2));
}

// ====================测试代码====================
void test(const char* testName, int length, int expected)
{
    if(length < 50)
    {
        // 递归效率太低,length较大时执行时间很长
        int result1 = maxProductAfterCutting_solution1(length);
        if(result1 == expected)
            std::cout << "Solution1 for " << testName << " passed." << std::endl;
        else
        std::cout << "Solution1 for " << testName << " FAILED." << std::endl;
    }

    int result2 = maxProductAfterCutting_solution2(length);
    if(result2 == expected)
        std::cout << "Solution2 for " << testName << " passed." << std::endl;
    else
        std::cout << "Solution2 for " << testName << " FAILED." << std::endl;

    int result3 = maxProductAfterCutting_solution3(length);
    if(result3 == expected)
        std::cout << "Solution3 for " << testName << " passed." << std::endl;
    else
        std::cout << "Solution3 for " << testName << " FAILED." << std::endl;
}

void test1()
{
    int length = 1;
    int expected = 0;
    test("test1", length, expected);
}

void test2()
{
    int length = 2;
    int expected = 1;
    test("test2", length, expected);
}

void test3()
{
    int length = 3;
    int expected = 2;
    test("test3", length, expected);
}

void test4()
{
    int length = 4;
    int expected = 4;
    test("test4", length, expected);
}

void test5()
{
    int length = 5;
    int expected = 6;
    test("test5", length, expected);
}

void test6()
{
    int length = 6;
    int expected = 9;
    test("test6", length, expected);
}

void test7()
{
    int length = 7;
    int expected = 12;
    test("test7", length, expected);
}

void test8()
{
    int length = 8;
    int expected = 18;
    test("test8", length, expected);
}

void test9()
{
    int length = 9;
    int expected = 27;
    test("test9", length, expected);
}

void test10()
{
    int length = 10;
    int expected = 36;
    test("test10", length, expected);
}

void test11()
{
    int length = 50;
    int expected = 86093442;
    test("test11", length, expected);
}

int main(int agrc, char* argv[])
{
    test1();
    test2();
    test3();
    test4();
    test5();
    test6();
    test7();
    test8();
    test9();
    test10();
    test11();

    return 0;
}

执行结果:

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值