返回

用动态规划找最长公共子序列

前端

路飞夜寻最长公共子序列,只为取经。且看动态规划算法如何助他一臂之力!

在算法的世界里,最长公共子序列(LCS)算法就如夜空中的一颗启明星,指引着程序员们在字符串的迷雾中寻找相似性。它能找出两个字符串中最长的连续子序列,而这个子序列在两个字符串中都存在。

动态规划算法,就像一个老练的猎人,通过一步步拆解问题,最终找到最佳解决方案。它将问题分解成一个个子问题,逐一解决,并将子问题的答案保存起来,避免重复计算。

对于LCS算法来说,其核心在于一个二维数组dp,其中dp[i][j]表示字符串s1的前i个字符与字符串s2的前j个字符的最长公共子序列的长度。

动态规划的奥秘

动态规划算法的魔力在于它那巧妙的递推公式:

dp[i][j] = {
  0,                                 if i == 0 or j == 0
  dp[i-1][j-1] + 1,                if s1[i] == s2[j]
  max(dp[i][j-1], dp[i-1][j]),    if s1[i] != s2[j]
}

从迷雾中寻踪觅迹

路飞夜寻LCS,算法为伴。每一步,他都会判断当前字符是否相等:

  • 如果相等,那么LCS长度加1。
  • 如果不相等,那么比较前一行的LCS长度和前一列的LCS长度,取最大值。

寻得至宝,功成身退

当路飞遍历完两行字符串时,二维数组的右下角dp[m][n]中保存的就是最长公共子序列的长度。

算法结束后,路飞使用回溯法,沿着二维数组中相等的元素,逐一找出最长公共子序列。

实战案例:JavaScript代码示例

function LCS(s1, s2) {
  const m = s1.length;
  const n = s2.length;
  const dp = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));

  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (s1[i - 1] === s2[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
      }
    }
  }
  
  let i = m;
  let j = n;
  let lcs = "";
  while (i > 0 && j > 0) {
    if (s1[i - 1] === s2[j - 1]) {
      lcs = s1[i - 1] + lcs;
      i--;
      j--;
    } else {
      if (dp[i - 1][j] > dp[i][j - 1]) {
        i--;
      } else {
        j--;
      }
    }
  }
  return lcs;
}

总结

动态规划算法,如同一座灯塔,照亮了LCS算法的寻宝之路。它将复杂问题分解成一个个小问题,逐一解决,最终指引路飞找到最长的公共子序列。算法的世界,妙趣横生,等待着你我继续探索!