键指offer——动态规划与贪婪算法+面试题14:剪绳子(p94-p98)

21 篇文章 0 订阅
14 篇文章 0 订阅

动态规划(dp,Dynamic Programming)

如果编程题是求一个问题的最优解(通常是最大值或最小值),而且该 问题能够分解为若干个子问题,并且子问题之间还有重叠的更小的子问题,就可以考虑用动态规划来解决。

动态规划求解的问题的四大特点:
  1. 求一个问题的最优解;
  2. 整体问题的最优解是依赖各个子问题的最优解;
  3. 大问题可以分成若干个子问题,这些子问题之间还有相互重叠的更小的子问题;
  4. 从上往下分析问题,从下往上求解问题。

由于子问题在分解大问题的过程中重复出现,为了避免重复求解子问题,可以从下往上的顺序先计算出小问题的最优解并存储下来(一般都是存在一位数组或者二维数组里),并把子问题的最优解组合起来逐步解决大的问题。

动态规划功能强大,它能够解决子问题并使用这些答案来解决大问题。但仅当每个子问题都是离散的,即不依赖于其他子问题时,动态规划才管用。

理解过程:背包问题

假设你是个小偷,背着一个可装4磅东西的背包。
在这里插入图片描述
方法就是使用动态规划,先解决子问题,再逐步解决大问题。
在这里插入图片描述
最初网格是空的,当网格被你填满之后,就找到了问题的答案。
最终结果如下:
其实,在计算每个单元格的价值时,使用的公式都是相同的,不论是填充值的时候还是计算最大值的时候:
在这里插入图片描述

练习:

在这里插入图片描述
方法就是之前的背包问题同样的解决方法:
在这里插入图片描述

总结:

 需要在给定约束条件下优化某种指标时,动态规划很有用。
 问题可分解为离散子问题时,可使用动态规划来解决。
 每种动态规划解决方案都涉及网格。
 单元格中的值通常就是你要优化的值。
 每个单元格都是一个子问题,因此你需要考虑如何将问题分解为子问题。
 没有放之四海皆准的计算动态规划解决方案的公式。

贪婪算法(贪心算法)

所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。)

所以,对所采用的贪心策略一定要仔细分析其是否满足无后效性。

该算法存在的问题:

不能保证求得的最后解是最佳的;
不能用来求最大值或最小值的问题;
只能求满足某些约束条件的可行解的范围。

贪婪算法适合用的问题:

贪心策略适用的前提是:局部最优策略能导致产生全局最优解。

实际上,贪心算法适用的情况很少。一般对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可以做出判断。

贪婪算法的优点——简单易行!贪婪算法很简单:每步都采取最优的做法。用专业术语说,就是你每步都选择局部最优解,最终得到的就是全局最优解。

在有些情况下,完美是优秀的敌人。有时候,你只需找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的结果又与正确结果相当接近。

面试题14:剪绳子

题目描述:
给你一根长度为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。

分析:

  • 首先是动态规划法来解题:将最优解存储在新建数组dp里,dp数组中的第i个元素就表示把长度为i的绳子剪成若干段之后各段长度乘积的最大值。求出每次剪绳子之后乘积的最大值,计算顺序自下而上。从绳子长度length=4开始就有了多种剪切方法:2*21*3,因此0-3为默认值lemgth[i],从length=4开始用动态规划计算最优值。

  • 接下来是贪婪算法,当length>=5时,我们尽可能多的剪长度为3的绳子,当剩下绳子长度为4时,将绳子剪成长度为2的绳子。这个思路还是蛮复杂的,比动态规划复杂一些,但解决起来又很简单。

代码:

  • 动态规划:
int maxProductAfterCutting_dp(int length)
{
	if(length == 0 || length ==1)
		return 0;
	if(length == 2)
		return 1;
	if(length == 3)
		return 2;
	
	int *dp = new int[length+1];//最终要求length的最优解
	dp[0] = 0;
	dp[1] = 1;
	dp[2] = 2;
	dp[3] = 3;
	int max=0;
	for(int i=4;i<length+1;i++)
	{
		for(int j=1;j<=i/2;j++)//求出将长度为i的绳子剪成若干段的乘积最大值放到dp[i]里,最终得到长度为length的绳子长度剪成若干段的乘积最大值
		{
			int x = dp[j]*dp[i-j];
			if(x > max)
				max = x;
			dp[i] = max;
		}
	}
	max = dp[length];//results
	delete[] length;
	
	return max;
}
  • 贪婪算法:
int maxProductAfterCutting_tanlan(int length)
{
	//-----------------0,1,2,3自行解决--------------------
	if(length == 0 || length ==1)
		return 0;
	if(length == 2)
		return 1;
	if(length == 3)
		return 2;
	//------------------仍然是长度大于等于4时开始有了多种减法------------------
	//尽可能多减去长度为3的绳子
	int timesOf3 = length/3;
	
	//判断当绳子剩下长度为4时,不能再减去长为3了,因为此时2*2的价值更大,所以3的倍数-1
	if(timesOf3 % 3 == 1)
		timesOf3 -= 1;
	//当剩下的值为0,2或者4时的操作,就是让其减去2
	int timesOf2 = (length - timesOf3*3)/2;
	//剩下绳子长度不可能为1,因为4里把1包括了;不可能为3因为3的倍数都会被减去
	return (int)(pow(3,timesOf3))*(int)(pow(2,timesOf2));//结果就是剪成长度为3或者2,所以有了这个操作
	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值