什么是 “动态规划” , 用两个经典问题举例。
2014-03-12 10:47
176 查看
原文地址:http://blog.csdn.net/fox64194167/article/details/21001855
1.什么是动态规划?
看了很多题解,一般解决者开始就说用DP来解,然后写了嵌套的for循环,不是很容易看懂,但是确实解出来了,我们这次来看下到底什么是动态规划?它有什么特点呢?容我抄一段话:动态规划(Dynamic programming,DP),通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。
这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
2.经典问题1:斐波那契数列(Fibonacci
polynomial)
我们直接用例子来举例:什么是斐波那契数列呢?
就是类似这样的数列:1,1,2,3,5,8,13,21 ...
我们这里从0开始,就是除了第0个数和第1个数为1以外,后面的数等于前面两个数之和。
2.1 用普通的递归来解斐波那契数列
[cpp] viewplaincopy
int fib(int n){
if(n == 0 || n == 1){
return 1;
}
return fib(n - 1) + fib(n - 2);
}
先不考虑负数,上面的写法是非常经典的递归。当n == 5的时候:fib(5)的计算过程如下:
fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
效率是非常低的,规模会成指数上升。这个解法有什么问题呢?就是没有保存一些已经计算过的值,老是重复计算。
2.2 改用动态规划来解
斐波那契数列
我们知道上面的问题在于没有保存一些已经计算的值,我这里用一个双向队列deque来保存前两个数。[cpp] view
plaincopy
int fib(int n)
{
deque<int> deq;
deq.push_back(1);
deq.push_back(1);
for(int i = 2; i <= n; i++){
deq.push_front(deq.front() + deq.back());
deq.pop_back();
}
return deq.front();
}
刚开始初始化两个1,当求第2个斐波那契数时,把前两个数,相加后保存到deq的头部,就变成了 2,1,1 ,再去除尾部的数变成 2,1,以此类推:
当求第3个斐波那契数时,deq就是 3,2,1-> 3,2
当求第4个斐波那契数时,deq就是 5,3,2-> 5,3
当求第5个斐波那契数时,deq就是 8,5,3-> 8,5
deq的头部就是当前要求的斐波那契数。我们的技巧就是保存了前面已经求解的数。
3.经典问题2 Triangle (Leetcode)
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.For example, given the following triangle
[ [2], [3,4], [6,5,7], [4,1,8,3] ]
The minimum path sum from top to bottom is
11(i.e., 2 + 3 +5 + 1 = 11).
这道题目就比上面的斐波那契数难些了。当弄下一个数时,还只能找相邻的。比如当前一步是5,那下一步只能是1或者8。也就是说下一步的index只可能等于当前index,或者index+1。
3.1用递归来解 Triangle
我们的思路是从头部开始,注意到递归主要要处理好参数和终止条件。我们这里的终止条件是到达三角形的最后一层。要不断变化的就是层数和index,因为我们要比较两个数。程序变量写的比较啰嗦,主要为了写的明白些。[cpp] view
plaincopy
int minPath(vector<vector<int> > triangle, int index, int level) {
if( level== triangle.size()-1) {
return triangle[level][index];
}else{
int current = triangle[level][index];
int nextFirst = minPath(triangle, index, level+1);
int nextSecond = minPath(triangle, index+1, level+1);
int nextFirstTotal = nextFirst + current;
int nextSecondTotal = nextSecond + current;
return nextFirstTotal < nextSecondTotal ? nextFirstTotal : nextSecondTotal;
}
}
int minimumTotal(vector<vector<int> > &triangle) {
if(triangle.size()==0) return 0;
return minPath(triangle, 0, 0);
}
3.2用DP来解Triangle
我们注意到递归非常慢,为了求第1层的最短距离,要先求第2层。为了求第2层,要先求第3层。铺了很多坑,最后全填上才能到结果。这个题目也很有意思,From top to bottom. 我们可以换个思路,从底部往上求。[ [2], [3,4], [6,5,7], [4,1,8,3] ]
我们可以用一个数组记录最后一行到当前行的最短距离,刚开始初始化为最后一行的数,就是4,1,8,3,然后求最后一行到倒数第2行的最短距离。因为 1 + 6 < 4 + 6, 1 + 5 < 8 + 5, 3 + 7 < 8 + 7。所以我们更新这个数组为7,6,10,3。最后一个3其实没用了。我们以此类推:
最后一行到倒数第3行的最短距离更新为: 9,10,10,3。
最后一行到倒数第4行的最短距离更新为:11,10,10,3。
第一个数11就是我们要的结果。
[cpp] view
plaincopy
int minimumTotal(vector<vector<int> > &triangle) {
int triangleSize = triangle.size();
if(triangleSize == 0){
return 0;
}
//初始化一个数组来记录最后一行到当前行的最短距离
vector<int> saveMinDistance(triangle[triangleSize - 1].size(), 0);
//刚开始的值为最后一行的值
for(int i = 0; i < triangle[triangleSize - 1].size(); ++i){
saveMinDistance[i] = triangle[triangleSize - 1][i];
}
int first,second,current;
first = second = current = 0;
//从倒数第2行开始求到第1行
for(int i = triangleSize - 2; i >=0; i--){
//当第N行,需要求N+1个最短距离,并且保存他们
for(int j = 0; j < i + 1; j++){
current = triangle[i][j];
first = current + saveMinDistance[j];
second = current + saveMinDistance[j + 1];
//保存最短距离
saveMinDistance[j] = first < second ? first : second;
}
}
return saveMinDistance[0];
}
稍微优化点,去除了初始化最后一行,也放到for循环中:
[cpp] view
plaincopy
int minimumTotal(vector<vector<int> > &triangle) {
int triangleSize = triangle.size();
if(triangleSize == 0){
return 0;
}
//初始化一个数组来记录最后一行到当前行的最短距离
vector<int> saveMinDistance(triangle[triangleSize - 1].size() + 1, 0);
int first,second,current;
first = second = current = 0;
//从倒数第1行开始求到第1行
for(int i = triangleSize - 1; i >=0; i--){
//当第N行,需要求N+1个最短距离,并且保存他们
for(int j = 0; j < i + 1; j++){
current = triangle[i][j];
first = current + saveMinDistance[j];
second = current + saveMinDistance[j + 1];
//保存最短距离
saveMinDistance[j] = first < second ? first : second;
}
}
return saveMinDistance[0];
}
总的来说动态规划还是比较难的技巧。因为首先当前题目要可以用动态规划的思想来做,其次要完整的把整个题目都想清楚,保存什么值,如何for循环,都是要考虑的因素。
我们这里用两道题目来窥探下动态规划,动态规划还是比较难的技巧,后期再见。
什么是
“动态规划” , 用两个经典问题举例。
相关文章推荐
- 什么是 “动态规划” , 用两个经典问题举例。
- 什么是 “动态规划” , 用两个经典问题举例
- 什么是 “动态规划” , 用两个经典问题举例。
- 什么是 “动态规划” , 用两个经典问题举例。
- 什么是 “动态规划” , 用两个经典问题举例。
- 什么是 “动态规划” , 用两个经典问题举例。
- 使用COM的解决1是什么2在哪里这两个问题
- 经典dp(动态规划)问题常用解决策略
- 基本算法复习之递归:经典问题举例
- C++/C经典算法百题--(39-42)年龄几何,三色球问题,两个正整数的最大公约数和(GCD)和最小公倍数(LCM)
- javascript中的=等号个数问题两个跟三个有什么区别
- 两个经典的Oracle触发器示例 update delete 锁表问题
- 【算法导论学习-29】动态规划经典问题02:最长公共子序列问题(Longest common subsequence,LCS)
- poj1837 动态规划和01背包问题延伸的经典题目,很值得一做
- 递归(动态规划)解决问题的应用方向(就是什么情况下用递归)+启发式优化算法+机器学习基础
- 动态规划中的经典问题
- 动态规划经典问题---最长公共子序列(LCS)
- 动态规划经典问题---矩阵链乘
- 经典问题:从输入网址到浏览器显示页面发生了什么
- 动态规划快速入门 之 经典的数塔问题