Longest Common Subsequence - JS Solution Space Optimized

JS
Editorial

Solution explanation

Solution explanation

Approach

Approach (Space-Optimized Dynamic Programming)

We compute the Longest Common Subsequence (LCS) length using a 1-row dynamic programming approach that stores only the necessary information from the previous iteration. To minimize the buffer size, we first ensure that text1 is the longer string—if not, we swap them.

  • Let m be the text1.length and n text2.length (after the swap if necessary).
  • Initialize a 1D array dpRow of length n + 1 with all values set to 0. This corresponds to the base case (row 0), where the LCS length is 0 for all prefixes of text2. After each iteration over a character of text1, dpRow[j] represents the LCS length between the processed prefix text1[0..i-1] and the prefix text2[0..j-1]. The array is updated in place as we move through each character of text1.

Iteration Logic

We update dpRow for each character in text1 (for row index 1..m). For each character text1[rowIndex-1], we iterate over all characters in text2 (for column index 1..n) and update dpRow[colIndex] in place. The values already stored in dpRow come from the previous text1 character and remain valid until they are overwritten, allowing us to reuse them directly during the current update.

Variables Used During Each Update

  • diagonal — a carried variable representing the LCS length before including the current characters. It starts at 0 at the beginning of each new iteration over text1, because column 0 corresponds to empty prefixes.
  • up — the value currently in dpRow[colIndex] before we overwrite it. This represents the LCS length if we exclude the current character of text1 (i.e., what we had from the previous iteration for the same j).
  • left — the already updated dpRow[colIndex-1], which corresponds to excluding the current character of text2.

Transition Rules

  • Match: if text1[i-1] === text2[j-1], we extend the previous best subsequence (stored in diagonal) by this matching character: dpRow[j] = diagonal + 1.
  • No Match: if the characters differ, we cannot include both current characters. We keep the longer subsequence found so far by excluding one of them: dpRow[j] = max(up, left).

After computing dpRow[j], we assign diagonal = up to carry forward the “old” value for the next column, since in the next step that will represent the case where both current characters were excluded.

Row Processing Flow

For each prefix of text1:

  • Start with diagonal = 0 (LCS is 0 for column 0).
  • At every position j, read the up value directly from dpRow[j] before it’s overwritten — it still holds the old value from the previous iteration.
  • After updating dpRow[j], assign diagonal = up before moving to the next j.
  • Once this iteration is done, dpRow holds the LCS lengths for all prefixes of text2 relative to text1[0..i-1].

Final Result

After processing all characters of text1, the final LCS length is stored in dpRow[n].

Invariant

After completing the updates for text1[i-1], every dpRow[j] represents the LCS length of text1[0..i-1] and text2[0..j-1]. When all iterations finish, dpRow[n] gives the final answer.

Time Complexity

O(m × n) We perform a comparison for every pair of characters of text1 and text2), resulting in m × n total iterations, where m = text1.length and n = text2.length

Space Complexity

O(min(m, n)) Only one row of size min(m, n) + 1 is maintained at a time where m = text1.length and n = text2.length

Visucodize editorial solution titled JS Solution Space Optimized for LeetCode's Longest Common Subsequence coding problem. Includes code and may include explanation. You can animate the code step by step using the provided input by clicking the run button, or fork it locally to update the code and input for custom visualization. View the original problem at https://leetcode.com/problems/longest-common-subsequence.