Skip to content
DSA in JavaScript
Dynamic Programming

Longest common subsequence problem

Algorithm · Core

Step through it: Dynamic Programming pattern

stairs
0
1
2
3
4
5
ways
·
·
·
·
·
·

Step 1 of 6

Goal: how many ways can you climb 5 stairs taking 1 or 2 steps at a time? You reach stair 5 from stair 4 or stair 3, so ways(5) = ways(4) + ways(3). Working that out naively repeats the same sub-questions many times. Instead write each answer down once.

The longest common subsequence (LCS) problem is the problem of finding the longest subsequence common to all sequences in a set of sequences (often just two sequences). It differs from the longest common substring problem: unlike substrings, subsequences are not required to occupy consecutive positions within the original sequences.

Application

The longest common subsequence problem is a classic computer science problem, the basis of data comparison programs such as the diff utility, and has applications in bioinformatics. It is also widely used by revision control systems such as Git for reconciling multiple changes made to a revision-controlled collection of files.

Example

  • LCS for input Sequences ABCDGH and AEDFHR is ADH of length 3.
  • LCS for input Sequences AGGTAB and GXTXAYB is GTAB of length 4.

Code

longestCommonSubsequence.js
/**
 * @param {string[]} set1
 * @param {string[]} set2
 * @return {string[]}
 */
export default function longestCommonSubsequence(set1, set2) {
  // Init LCS matrix.
  const lcsMatrix = Array(set2.length + 1).fill(null).map(() => Array(set1.length + 1).fill(null));

  // Fill first row with zeros.
  for (let columnIndex = 0; columnIndex <= set1.length; columnIndex += 1) {
    lcsMatrix[0][columnIndex] = 0;
  }

  // Fill first column with zeros.
  for (let rowIndex = 0; rowIndex <= set2.length; rowIndex += 1) {
    lcsMatrix[rowIndex][0] = 0;
  }

  // Fill rest of the column that correspond to each of two strings.
  for (let rowIndex = 1; rowIndex <= set2.length; rowIndex += 1) {
    for (let columnIndex = 1; columnIndex <= set1.length; columnIndex += 1) {
      if (set1[columnIndex - 1] === set2[rowIndex - 1]) {
        lcsMatrix[rowIndex][columnIndex] = lcsMatrix[rowIndex - 1][columnIndex - 1] + 1;
      } else {
        lcsMatrix[rowIndex][columnIndex] = Math.max(
          lcsMatrix[rowIndex - 1][columnIndex],
          lcsMatrix[rowIndex][columnIndex - 1],
        );
      }
    }
  }

  // Calculate LCS based on LCS matrix.
  if (!lcsMatrix[set2.length][set1.length]) {
    // If the length of largest common string is zero then return empty string.
    return [''];
  }

  const longestSequence = [];
  let columnIndex = set1.length;
  let rowIndex = set2.length;

  while (columnIndex > 0 || rowIndex > 0) {
    if (set1[columnIndex - 1] === set2[rowIndex - 1]) {
      // Move by diagonal left-top.
      longestSequence.unshift(set1[columnIndex - 1]);
      columnIndex -= 1;
      rowIndex -= 1;
    } else if (lcsMatrix[rowIndex][columnIndex] === lcsMatrix[rowIndex][columnIndex - 1]) {
      // Move left.
      columnIndex -= 1;
    } else {
      // Move up.
      rowIndex -= 1;
    }
  }

  return longestSequence;
}
longestCommonSubsequenceRecursive.js
/* eslint-disable no-param-reassign */
/**
 * Longest Common Subsequence (LCS) (Recursive Approach).
 *
 * @param {string} string1
 * @param {string} string2
 * @return {number}
 */
export default function longestCommonSubsequenceRecursive(string1, string2) {
  /**
   *
   * @param {string} s1
   * @param {string} s2
   * @return {string} - returns the LCS (Longest Common Subsequence)
   */
  const lcs = (s1, s2, memo = {}) => {
    if (!s1 || !s2) return '';

    if (memo[`${s1}:${s2}`]) return memo[`${s1}:${s2}`];

    if (s1[0] === s2[0]) {
      return s1[0] + lcs(s1.substring(1), s2.substring(1), memo);
    }

    const nextLcs1 = lcs(s1.substring(1), s2, memo);
    const nextLcs2 = lcs(s1, s2.substring(1), memo);

    const nextLongest = nextLcs1.length >= nextLcs2.length ? nextLcs1 : nextLcs2;

    memo[`${s1}:${s2}`] = nextLongest;

    return nextLongest;
  };

  return lcs(string1, string2);
}

References

Practice

Spotted a mistake or something unclear? Report it on GitHub.

On this page