动态规划之最长公共子序列问题(LCS)
最长公共子序列问题
设计一个函数输出两个字符串的最长公共子序列
例如:' abc '和' ace '的最长公共子序列为' ac '。
这个问题是分治法的经典问题,具体思路可以这样考虑
比较两个字符串的首位(str1[0], str2[0]),如果相等的话,那么结果则为 str1[0] + f(str1.substr(1), str2.substr(2));
如果首位不相等的话,则分为两种情况:
f(str1, str2.substr(1)) 第一个字符串不变和去掉一个字符的第二个字符串相比
f(str1.substr(1), str2) 第二个字符串不变和去掉第一个字符的第一个字符串相比
然后我们从这两种结果中挑选最长的返回
那么所有的比较都可以大致的用上面的两种情况来递归讨论,所以我们不难写出算法
function LCS(str1, str2) {
if(!str1 || !str2) return "";
if(str1[0] == str2[0]) {
return str1[0] + LCS(str1.substr(1), str2.substr(1));
} else {
var s1 = LCS(str1, str2.substr(1));
var s2 = LCS(str1.substr(1), str2);
if(s1.length > s2.length) {
return s1;
} else {
return s2;
}
}
}当然用分治法是可以解决这个问题,但我们不难发现一次次的递归不仅时间复杂度高,并且其中存在非常多重复的运算,我们是不是可以把已经算过的结果保存在一个数据结构中,下次递归前先看看这个数据结构中是否有我们需要的值,有的话就不用进行重复的递归了
所以出于以上思路,我们可以把这个算法优化一下
function LCS(str1, str2) {
var total = []; //使用一个全局变量来存储每次的结果
function _LCS(str1, str2) {
if (!str1 || !str2) return "";
//判断如果这次要递归的两个字符串已经在total中出现了,那么直接返回太多结果
total.forEach(item => {
if(item.str1 == str1 && item.str2 == str2) {
return item.result;
}
})
var newResult;
if (str1[0] == str2[0]) {
newResult = str1[0] + _LCS(str1.substr(1), str2.substr(1));
} else {
var s1 = _LCS(str1, str2.substr(1));
var s2 = _LCS(str1.substr(1), str2);
if (s1.length > s2.length) {
newResult = s1;
} else {
newResult = s2;
}
}
// 每次运算都把结果保存在total中
total.push({
str1,
str2,
result: newResult
})
return newResult;
}
return _LCS(str1, str2);
}
这个算法中,我们牺牲了空间复杂度来换取了时间复杂度,减少了不必要的重复运算,其实这就是动态规划和分治法的一个差异,他们思想是相同的,但是动态规划在分治的基础上加了一层存储,降低了时间复杂度。
