动态规划(简称DP)的思想是把一个大的问题进行拆分,细分成一个个小的子问题,且能够从这些小的子问题的解当中推导出原问题的解。
一、性质
1、最优子结构性:既所拆分的子问题的解是最优解。
2、无后效性:即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策略影响。
3、子问题重叠性质: 既在求解的过程当中,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的解题效率。
二、典型问题
斐波那契数列的递推式就是动态规划的一种体现
f(i)=f(i−1)+f(i−2),i>=3
从递归搜索的角度去思考斐波那契数列
function foi(n)
{
if(n===1 || n===2){
return 1;
}else{
return foi(n-1)+foi(n-2)
}
}
console.time('time') //算法的开始时间
// var beginTime = new Date();
let str = ""
for(let i=3;i<=20;i++){
str += foi(i)+"\t"
}
console.log(str)
console.timeEnd('time') //算法的结束时间
// var endTime = new Date();
// console.log("用时共计"+(endTime-beginTime)+"ms");
我们会发现上面的程序在超过50项之后计算速度就会以肉眼可见的速度下降,原因是什么呢,就是因为该程序在计算的过程中遇到了大量的子问题的重叠计算。
解决办法:动态规划
const f = new Array()
function dp(n)
{
f[1] = 1;f[2] = 1;
for(let i = 3;i <= n ;i ++)
f[i] = f[i-1] + f[i-2];
}
console.time('time') //算法的开始时间
dp(50)
console.timeEnd('time') //算法的结束时间
let str = ""
for(let i=3;i<f.length;i++){
str += f[i]+"\t"
}
console.log(str)
三、求解的基本步骤
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示:
初始状态→│决策1│→│决策2│→…→│决策n│→结束状态
1、划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
2、确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
3、确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
4、寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)
四、示例
爬梯子问题
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:输入: 2 输出: 2
解释: 有两种方法可以爬到楼顶。
a、1 阶 + 1 阶 b、2 阶
示例 2:输入: 3 输出: 3
解释: 有三种方法可以爬到楼顶。
a、1 阶 + 1 阶 + 1 阶 b、1 阶 + 2 阶 c、2 阶 + 1 阶
走1阶台阶只有一种走法,但是走2阶台阶有两种走法(如示例1),如果n是双数,我们可以凑成m个2级台阶,每个m都有两种走法,如果n是单数,那么可以凑成m个2级台阶加上一个1级台阶,这样就是一个排列组合题目,但是开销比较大。
代码如下:
//爬楼梯问题
function climbStairs(n) {
if (n === 1 || n === 2) {
return n;
}
var ways = [];
ways[0] = 1;
ways[1] = 2;
for(var i=2; i<n; i++){
ways[i]=ways[i-1] + ways[i-2];
}
return ways[n-1];
}
console.log(climbStairs(3));//3