本文主要是介绍键指offer——动态规划与贪婪算法+面试题14:剪绳子(p94-p98),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 动态规划(dp,Dynamic Programming)
- 动态规划求解的问题的四大特点:
- 理解过程:背包问题
- 练习:
- 总结:
- 贪婪算法(贪心算法)
- 该算法存在的问题:
- 贪婪算法适合用的问题:
- 面试题14:剪绳子
动态规划(dp,Dynamic Programming)
如果编程题是求一个问题的最优解(通常是最大值或最小值),而且该 问题能够分解为若干个子问题,并且子问题之间还有重叠的更小的子问题,就可以考虑用动态规划来解决。
动态规划求解的问题的四大特点:
- 求一个问题的最优解;
- 整体问题的最优解是依赖各个子问题的最优解;
- 大问题可以分成若干个子问题,这些子问题之间还有相互重叠的更小的子问题;
- 从上往下分析问题,从下往上求解问题。
由于子问题在分解大问题的过程中重复出现,为了避免重复求解子问题,可以从下往上的顺序先计算出小问题的最优解并存储下来(一般都是存在一位数组或者二维数组里),并把子问题的最优解组合起来逐步解决大的问题。
动态规划功能强大,它能够解决子问题并使用这些答案来解决大问题。但仅当每个子问题都是离散的,即不依赖于其他子问题时,动态规划才管用。
理解过程:背包问题
假设你是个小偷,背着一个可装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*2
和1*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];//resultsdelete[] 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的倍数-1if(timesOf3 % 3 == 1)timesOf3 -= 1;//当剩下的值为0,2或者4时的操作,就是让其减去2int timesOf2 = (length - timesOf3*3)/2;//剩下绳子长度不可能为1,因为4里把1包括了;不可能为3因为3的倍数都会被减去return (int)(pow(3,timesOf3))*(int)(pow(2,timesOf2));//结果就是剪成长度为3或者2,所以有了这个操作}
这篇关于键指offer——动态规划与贪婪算法+面试题14:剪绳子(p94-p98)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!