Skip to content

1143. Longest Common Subsequence

class Solution:
    """🔗 Longest Common Subsequence.

    Problem:
    --------
    Given two strings `text1` and `text2`, return the length of their longest common
    subsequence, or 0 if there is none. A subsequence keeps the relative order of the
    characters but may skip some of them.

    Key Insight:
    ------------
    This is Edit Distance (LC 72) with substitution forbidden: the only way to handle a
    mismatched character is to delete it, so the best alignment keeps the most matches.
    Let `D[i][j]` be the LCS length of `text1[:i]` and `text2[:j]`, and look at the last
    characters:
    - **Equal**: they extend the LCS of both prefixes, `D[i][j] = D[i-1][j-1] + 1`.
    - **Different**: at least one of them is not in the LCS, so drop one,
      `D[i][j] = max(D[i-1][j], D[i][j-1])`.

    Base cases: `D[i][0] = D[0][j] = 0` (an empty string has no common subsequence).

    Example (`text1 = "abcde"`, `text2 = "ace"`, answer `D[5][3] = 3`):

              ""  a  c  e
          ""   0  0  0  0
          a    0  1  1  1
          b    0  1  1  1
          c    0  1  2  2
          d    0  1  2  2
          e    0  1  2  3

    Approach:
    ---------
    - Shortcut: identical strings are their own LCS.
    - Row `i` depends only on row `i - 1` (`D[i-1][j]`, `D[i-1][j-1]`) and on the cell to
      its left in row `i` (`D[i][j-1]`), so two rows suffice. Each row starts at
      `D[i][0] = 0` and fills left to right.

    Complexity:
    -----------
    - Time: O(l1 * l2)
    - Space: O(l2), down from O(l1 * l2) for the full table
    """

    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        l1, l2 = len(text1), len(text2)
        if text1 == text2:
            return l1

        prev_row = [0] * (l2 + 1)

        for i in range(1, l1 + 1):
            curr_row = [0] * (l2 + 1)
            for j in range(1, l2 + 1):
                if text1[i - 1] == text2[j - 1]:
                    curr_row[j] = prev_row[j - 1] + 1
                else:
                    curr_row[j] = max(prev_row[j], curr_row[j - 1])
            prev_row = curr_row

        return prev_row[-1]