认识动态规划
动态规划,听起来高大上,其实并不难,在你看完这篇博客后还可能感叹,这么简单呀!
在了解动态规划之前,我们得先谈谈递归,在我们的js里递归的本质就是在函数执行栈中调用函数,一层一层的深入,直到小问题被解决,开始回溯,最后大问题被解决;
递归虽然代码简洁,但是执行效率低下,使用动态规划设计的算法从它能解决的最简单的子问题开始,继而通过得到的解,去解决其他更复杂的子问题,直到整个问题都被解决。所有子问题的解通常被存储在一个数组里以便于访问。
动态规划案例
计算斐波那契数列
什么是斐波那契数列呢?0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …
从第三项开始,前两项数值之和组成下一个数值;
那我想传入一个序列号,然后程序给我返回一个对应斐波那契数列中对应的值(从0开始);
先来看看递归,那真是简洁哈。
function recurFib(n) {
if (n < 2) {
return n;
}
else {
return recurFib(n-1) + recurFib(n-2);
}
}
用动态规划来试试,我们得要一个数组来存储子问题的解,空间复杂度这里就不探究了哈;
function dynFib(n) {
if (n === 0) return n;
if (n == 1 || n == 2) return 1;
var val = [];
// 将数组填充为0
for (var i = 0; i <= n; ++i) {
val[i] = 0;
}
// 初始化数组,将序列1、2位赋值,方便计算
val[1] = 1;
val[2] = 2;
for (var i = 3; i <= n; ++i) {
val[i] = val[i - 1] + val[i - 2];
}
return val[n - 1];
}
就是用数组保存一下已经求得到的值,不过这里下标为2的值保存的是斐波那契数列中下标为3的值,所以在返回结果时要-1;
寻找最长公共子串
另一个适合使用动态规划去解决的问题是寻找两个字符串的最长公共子串。例如,在单词“raven”和“havoc”中,最长的公共子串是“av”。
这个LCS(最长公共子串)的逻辑需要稍微的理解一下:首先两个字符串在相同下标值时比较两个值是否相等,这个容易撒,但是我们的题目是要找最长的公共子串,也就是说在比较时下标值不能固定,而且我们得要记录一下的连续相等的长度、索引,所以动态规划是不二的选择;
思路是用二维数组lcsarr来记录一下相等时的情况,假设传入的参数是 str1 和 str2;
我们将0填充到二维数组里面;
我们先取到str1[0],在依次于str2的每个值比较,相同则在lcsarr对应的位置上赋值为1;
重点来了哈,str1[1],与str2[0],比较时相同也是赋值为1,但是0后面的下标值j对应的值与str1[1]相同时,
需要在lcsarr[0][j-1]基础上加1;
也就是说如果两个字符串相应位置的字符进行了匹配,当前数组元素的值将被设置为前一次循环中数组元素保存的值加 1;
这么说有点抽象,它在二维数组中的表现就是斜着的一条线上相加,这样来记录连续相等的值,我来手画个图:
完整代码:
function lcs(str1, str2) {
// 1.初始化
// let lcsarr = new Array(str1.length).fill(new Array(str2.length).fill(0));同一个索引
let lcsarr = new Array(str1.length);
let max = 0;
let index = null;
// 2.遍历二维数组
for (let i = 0; i < str1.length; i++) {
lcsarr[i] = new Array(str2.length).fill(0);
for (let j = 0; j < str2.length; j++) {
// 2.1相等就将让temp[i][j]相对于temp[i-1][j-1]加一
// 看看str1上一个的值和str2上一个对比的值是否一样,一样就是连续滴
if (str1[i] == str2[j]) {
// 2.2记录
if (i > 0 && j > 0) {
lcsarr[i][j] = 1 + lcsarr[i - 1][j - 1];
} else {
lcsarr[i][j] = 1;
}
// 2.3记录max,index
if (max < lcsarr[i][j]) {
max = lcsarr[i][j];
index = i;
}
} else {
lcsarr[i][j] = 0;
}
}
}
//3.1返回值处理
if (index === null) return null;
// 3.2要算上开始的位置所以要加一
return str1.substr(index - max + 1, max);
}
let str1 = "coveccxaaae";
let str2 = "acveccxaf";
console.log(lcs(str1, str2));
这里初始化二维数组时不要像我第一次一样:let lcsarr = new Array(str1.length).fill(new Array(str2.length).fill(0))
全填充为一个相同的内存地址了;