前段时间一直写了几个算法题目,发现有个很牛逼的算法,动态规划,虽然有的解题思路和动态规划很像,但是当时不知道其中的原理和一些通用性,接下来的几天,通过一些栗子一点一点揭开动态规划那神秘的面霜,我也是现学现卖的,如果有那里写错的欢迎给我留言指正。
动态规划有时被称为递归的相反的技术。递归是从顶部开始将问题分解,通过解决所有分解小问题的方式,来解决整个问题。而动态规划这是从底部开始解决问题,将所有小问题解决掉,然后合并成整体的解决方案,从而解决掉整个大问题。递归方式虽然很简洁,但是效率不高,但是不能说递归是不好的,本质上是,命令式语言和面向对象的语言对递归的实现不够完善,因为它们没有将递归作为高级编程特性。
动态规划方案通常使用一个数组来建立一张表,用于存放被分解成众多子问题的解。当算法执行完毕,最终的解法将会在这个表中找到。
今天我们先从我们最熟的斐波那契数列数列开始。
0, 1, 1, 2, 3, 5, 8, 13, 21, 24, 55, ...
从数列中可以发现从第三个数开始的值是前两个值的和。
递归解法
function fib(n){
if(n < 2){
return n;
}else{
return fib(n - 1) + fib(n - 2);
}
}
console.log(fib(10)); // 55
动态规划解法
function fibDyn(n){
var temp = [];
for(var i = 0; i <= n; i++){
temp[i] = 0
}
if(n == 1 || n == 2){
return 1;
}else{
temp[1] = 1;
temp[2] = 2;
for(var i = 3; i < n; i++){
temp[i] = temp[i - 1] + temp[i -2];
}
return temp[i - 1];
}
}
fibDyn(10) // 55
从程序中我们可以看出,初始化了一个和传入等长的空数组,去存放每次运算厚的结果。
测试程序运行时间
var start = new Date().getTime();
console.log(fib(10))
var stop = new Date().getTime();
console.log('递归消耗' + (stop - start) + '毫秒');
start = new Date().getTime();
console.log(fibDyn(10))
stop = new Date().getTime();
console.log('动态规划消耗' + (stop - start) + '毫秒');
运行结果
55
递归消耗-- 0 毫秒
55
动态规划消耗-- 0 毫秒
fib(20)
6765
递归消耗-- 1 毫秒
6765
动态规划消耗-- 0 毫秒
fib(30)
832040
递归消耗-- 17 毫秒
832040
动态规划消耗-- 0 毫秒
改变fib(n)中的 n 的值我们会发现,动态规划的解决方案姚比递归解决方案高效很多。
优化斐波那契数列的动态规划算法
我们看到这个动态规划的算法是要了一个空数组,这是你可能已经想到使用迭代的方案计算,可以不使用数组,需要用到的素组的原因事因为动态规划算法通常需要吧中间的结果保存起来。一下是优化的迭代版。
function fibDyn(n){
var prev = 1;
var middle = 1;
var result = 1;
for(var i = 2; i < n; i++){
result = prev + middle;
prev = middle;
middle = result;
}
return result;
}
fibDyn(10) // 55
这时候我们可以看到少了创建数组这一步,效率没变,但是空间复杂度变小了。
斐波那契数列在很多地方都会用上,我是从一个台阶问题发现的,同时知道了动态规划的解法。有兴趣的可以在公众号中回去“台阶问题”