第十七节:动态规划详解之斐波那契数列(递归、记忆搜索、动态规划、状态压缩)
一. 动态规划详解
1. 定义
动态规划(英语:Dynamic programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
动态规划也是互联网大厂和算法竞赛中非常喜欢考察的一类题目:因为通过动态规划可以很好的看出一个人的思考问题的能力、逻辑的强度、程序和算法的设计等等。
那么通过学习动态规划,可以提高算法设计和分析的能力,为解决复杂问题提供强有力的工具和思路。
2. 解题步骤
动态规划的核心思想是“将问题划分为若干个子问题,并在计算子问题的基础上,逐步构建出原问题的解”
步骤1:定义状态。
✓ 将原问题划分为若干个子问题,定义状态表示子问题的解,通常使用一个数组或者矩阵来表示。 let dp:number[]=[];
步骤2:确定状态转移方程。
✓ 在计算子问题的基础上,逐步构建出原问题的解。
✓ 这个过程通常使用“状态转移方程”来描述,表示从一个状态转移到另一个状态时的转移规则。
步骤3:初始化状态。
步骤4:计算原问题的解(最终答案)。
✓ 通过计算状态之间的转移,最终计算出原问题的解。
✓ 通常使用递归或者迭代(循环)的方式计算
注:其中步骤2 和 步骤3 在代码中的位置可能颠倒
二. 斐波那契数列-递归
1. 题目说明
第 0 个和第 1 个斐波那契数分别为0和1,即 F0 = 0, F1 = 1。 (也有一种说法,F0=1)
从第 2 个数开始,每个斐波那契数都是它前面两个斐波那契数之和,即F2 = F0 + F1,F3 = F1 + F2,F4 = F2 + F3,以此类推。
求第 n 个数列?
2. 方案1-递归求解
(1). 思路分析
A. 确认递归结束的条件 n=0 或 1 结束
B. 确认递归公式 fib(n)=fib(n-1)+fib(n-2)
function fib(n: number): number {
// if (n === 0) return 0;
// if (n === 1) return 1;
//上述两句可以简洁写法
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
//测试
// 0 1 1 2 3 5 8 13 21 34 55
{
const startTime2 = performance.now();
console.log(fib(10));
const endTime2 = performance.now();
console.log(`fib(10)消耗的时间:${endTime2 - startTime2}毫秒`);
}
{
const startTime2 = performance.now();
console.log(fib(50)); //很慢
const endTime2 = performance.now();
console.log(`fib(50)消耗的时间:${endTime2 - startTime2}毫秒`);
}
(2). 存在的问题
存在大量的重复计算,比如 fib(9) 需要计算8和7; fib(8) 需要计算7和6, 很明显 7计算重复,随着数量越来越大,重复的数据也越来越多
(3). 性能测试
fib(10) 2毫秒
fib(50) 121651毫秒
三. 记忆搜索
1. 含义
记忆化搜索(Memoization)的技巧,将已经计算过的结果保存下来,以便在后续的计算中直接使用。
记忆化搜索可以极大地提高递归算法的效率,特别是对于有大量重复计算的问题,优化效果尤为明显。 这种解法也可以称之为自顶向下的解法。
2. 思路分析
之前方案1中存在大量的重复计算,导致计算缓慢,所以这里采用记忆化搜索,
在递归的基础上,将算好数列值存放到数组中,无须重复计算,直接从数组中获取即可。
核心代码:dp[n] = fib(n - 1, dp) + fib(n - 2, dp); 调用的时候必须传入dp,从而保证至始至终是同一个dp。
/**
* 方案2
* @param n 求n个数列
* @param dp 存放的记忆数组
* @returns 返回第n个数列的值
*/
function fib(n: number, dp: number[] = []): number {
//上述两句可以简洁写法
if (n <= 1) return n;
//直接从dp中取值
if (dp[n]) {
return dp[n];
}
//dp数组中不存在,则递归计算
dp[n] = fib(n - 1, dp) + fib(n - 2, dp);
return dp[n];
}
//测试
// 0 1 1 2 3 5 8 13 21 34 55
{
const startTime2 = performance.now();
console.log(fib(10));
const endTime2 = performance.now();
console.log(`fib(10)消耗的时间:${endTime2 - startTime2}毫秒`);
}
{
const startTime2 = performance.now();
console.log(fib(50)); //很快 值:12586269025
const endTime2 = performance.now();
console.log(`fib(50)消耗的时间:${endTime2 - startTime2}毫秒`);
}
3. 性能测试
fib(10) 1毫秒内
fib(50) 1毫秒内
四. 动态规划
1. 步骤划分详细分析
(1).定义状态
dp数组保留斐波那契数列中每一个位置对应的值(状态)
dp[x]表示的就是x位置对应的值(状态)
(2).设置初始化状态
dp[0]/dp[1]初始化状态
(3).状态转移方程
dp[i] = dp[i-1] + dp[i-2]; 状态转移方程一般情况都是写在循环(for/while)中
(4).计算最终的结果
function fib(n: number): number {
//1.定义状态
let dp: number[] = [];
//2. 初始化状态
dp[0] = 0;
dp[1] = 1;
//3. 确认状态转换方程
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
//4.求的最终结果
return dp[n];
}
/**
* 测试
* 0 1 1 2 3 5 8 13 21 34 55
*/
{
const startTime2 = performance.now();
console.log(fib(10));
const endTime2 = performance.now();
console.log(`fib(10)消耗的时间:${endTime2 - startTime2}毫秒`);
}
{
const startTime2 = performance.now();
console.log(fib(50)); //很快 值:12586269025
const endTime2 = performance.now();
console.log(`fib(50)消耗的时间:${endTime2 - startTime2}毫秒`);
}
2. 性能测试
fib(10) 1毫秒内
fib(50) 1毫秒内
3. 总结
动态规划算法可以看作是记忆化搜索的一种扩展,它通常采用自底向上的方式计算子问题的结果,并将结果保存下来以便后续的计算使用
对于斐波那契数列问题来说,我们采用自底向上的方式计算子问题的结果,确保 dp[i-1] 和 dp[i-2] 的值已经计算出来了,才能计算 dp[i] 的值。
可以继续优化的点:dp数组,如果n很大,dp中占用的内存空间则很大,能不能优化一下呢
五. 状态压缩
1. 含义
在动态规划算法中,有一种常见的优化方法叫做状态压缩,可以将状态的存储空间从数组优化为一个常数.
2. 步骤分析
(1). 定义状态
变量pre用来保存前一个值,current保存当前值
(2). 设置初始化状态
pre 和 current 初始化值
(3). 状态转移方程
let res = current + pre; 然后重新给 pre和current赋值
(4). 计算最终的结果
current即为最终的值
function fib(n: number): number {
if (n <= 1) return n;
//1.定义状态 和 2. 初始化状态
let pre = 0;
let current = 1;
//3. 确认状态转换方程
for (let i = 2; i <= n; i++) {
let res = current + pre;
pre = current;
current = res;
}
//4.求的最终结果
return current;
}
3. 性能测试
fib(10) 1毫秒内
fib(50) 1毫秒内
4. 总结
采用两个变量实现和前面的动态规划实现相比,减少了存储空间的使用,优化了空间复杂度
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。