《剑指offer》面试题14:剪绳子(动态规划、贪婪算法)

更多剑指offer面试习题请点击: 《剑指offer》(第二版)题集目录索引

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


解题思路:
动态规划法:

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

这道题很明显复合这四个特征故我们可以使用动态规划求解。

动态规划:

  1. 有一段长度为n的绳子,我们现在要剪第一刀,我可以选择下第一刀的地方有1n-1这些地方;比如长度为10的绳子,我第一刀可以在19这些地方下刀,共9种方式。

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

  3. 那么如何判断i处切最大呢?这个时候,我们就要知道,[0~i]这个长度的绳子,任意方式切,最大的乘积是多少;假如说,当我们要切一个长度为10的绳子:切成1和9与4和6,两种方式,哪个乘积更大?
    回答:不光要考虑第一刀后两个绳子的大小,还要考虑到9、4、6这三种情况,因为第一刀切出的绳子长度是否可以再切第二刀,使它有更大的乘积,比如将9再切成 3 × 3 × 3 3×3×3 3×3×3,6切成 4 × 2 4×2 4×2,哪个更大?

  这种情况下,我们可以发现,无论再怎么切,一定是越切越短,那么我们是否可以将小于给定长度的绳子的每一个长度的最大乘积都求出来?
即:长度为10的绳子,我们就计算出:长度1~9这9种长度的绳子,每种长度的最大乘积是多少。
  要求长度9的绳子的最大乘积,我们要知道18各个长度的最大乘积,要知道长度8的最大乘积,就要知道17长度的各个最大乘积,以此类推。


贪婪算法:
当n大于等于5时,我们尽可能多的剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子。 为什么选2,3为最小的子问题?因为2,3包含于各个问题中,如果再往下剪得化,乘积就会变小。 为什么选长度为3?因为当n≥5时, 3 ( n − 3 ) ≥ 2 ( n − 2 ) 3(n-3)≥2(n-2) 3(n3)2(n2)

效率分析:
动态规划:空间复杂 O ( n ) O(n) O(n),时间复杂O ( n 2 ) (n^2) (n2)
贪婪算法:空间时间均为 O ( 1 ) O(1) O(1)


< CuttingRope.c >

/*************************动态规划******************************/
int maxProductAfterCutting_solution1(int length) 
{
	if (length < 2)
		return 0;
	if (length == 2)
		return 1;
	if (length == 3)
		return 2;

	int* products = (int*)malloc((length + 1)*sizeof(int));
	assert(products != NULL);
	memset(products, 0, (length + 1)*sizeof(int));
	
	/****长度为0、1、2、3的绳子的长度********/
	products[0] = 0;
	products[1] = 1;
	products[2] = 2;
	products[3] = 3;

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

			products[i] = max;
		}
	}
	max = products[length];

	free(products);
	products = NULL;

	return max;
}


/*********************贪婪算法*********************************/
int maxProductAfterCutting_solution2(int length) 
{
	if (length < 2)
		return 0;
	if (length == 2)
		return 1;
	if (length == 3)
		return 2;

	int timesOf3 = length / 3;

	/*  当最后绳子长度为 4 时,这时候分割成 2,2 而不是 3,1 因为2*2=4 > 3=3*1  */
	if (length - timesOf3 * 3 == 1)
		timesOf3--;

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

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

< test.c >

void test(const char* testName, int length, int expected)
{
	int result1 = maxProductAfterCutting_solution1(length);
	if (result1 == expected)
		printf("Solution1 for %s passed.\n", testName);
	else
		printf("Solution1 for %s FAILED.\n", testName);

	int result2 = maxProductAfterCutting_solution2(length);
	if (result2 == expected)
		printf("Solution2 for %s passed.\n\n", testName);
	else
		printf("Solution2 for %s FAILED.\n\n", testName);
}

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()
{
	test1();
	test2();
	test3();
	test4();
	test5();
	test6();
	test7();
	test8();
	test9();
	test10();
	test11();

	system("pause");
	return 0;
}

这里写图片描述

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值