Subsequence Problems

Subsequence DP compares two strings (or one string against itself) by building a 2D table over prefixes. This lesson covers the longest common subsequence (LCS, LeetCode 1143), longest palindromic subsequence (516), related variants, and full solutions in C, Java, and Python with trace tables.

Overview

A subsequence keeps characters in order but may skip any number of them. Subsequence DP typically uses:

State:  dp[i][j] = answer for first i chars of A and first j chars of B
Base:   dp[0][*] = dp[*][0] = 0  (empty prefix)

Match:   dp[i][j] = dp[i-1][j-1] + 1        (or +2 for palindrome pairs)
Mismatch: dp[i][j] = max(dp[i-1][j], dp[i][j-1])   (LCS)
          dp[i][j] = max(dp[i+1][j], dp[i][j-1])   (LPS, fill backwards)
Problem Goal State LeetCode
LCSLongest common subsequence of two stringsdp[i][j] on prefixes1143
LPSLongest palindromic subsequence in one stringdp[i][j] on substring s[i..j]516
Delete ops for two stringsMin deletions to make equalLCS length → m+n−2·LCS583
Distinct subsequencesCount s2 subsequences in s1dp[i][j] count115
Longest common substringContiguous match onlyReset to 0 on mismatchCustom

Subsequence vs Substring vs Subarray

Term Contiguous? Order preserved? Example in "abcde"
SubsequenceNoYes"ace", "bd", "a"
SubstringYesYes"bcd", "ab", "e"
SubarrayYes (array)YesSame as substring for strings
Interview trap: LCS allows gaps; longest common substring requires contiguous blocks — on mismatch set dp[i][j]=0, not max of neighbors.

Longest Common Subsequence (LCS)

Problem (LeetCode 1143): Given text1 and text2, return the length of their longest common subsequence.

State: dp[i][j] = LCS length of text1[0..i−1] and text2[0..j−1].

Recurrence:

  • If text1[i−1] == text2[j−1]: dp[i][j] = dp[i−1][j−1] + 1
  • Else: dp[i][j] = max(dp[i−1][j], dp[i][j−1])

Answer: dp[m][n] where m, n are string lengths.

LCS Code (C, Java, Python)

Keywords Types Functions Variables Strings Numbers Comments

Python

def longest_common_subsequence(text1, text2):
    """LeetCode 1143 — LCS length of two strings. O(m × n)."""
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i - 1] == text2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1   # match: extend LCS
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])  # skip one char

    return dp[m][n]


if __name__ == "__main__":
    tests = [("abcde", "ace"), ("abc", "abc"), ("abc", "def")]
    for t1, t2 in tests:
        print(f"LCS('{t1}', '{t2}') = {longest_common_subsequence(t1, t2)}")

Java

public class LongestCommonSubsequence {

    /** LCS length — classic 2D DP on string prefixes. */
    public static int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m + 1][n + 1];

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }

    public static void main(String[] args) {
        System.out.println(longestCommonSubsequence("abcde", "ace")); // 3
        System.out.println(longestCommonSubsequence("abc", "abc"));     // 3
        System.out.println(longestCommonSubsequence("abc", "def"));   // 0
    }
}

C

#include <stdio.h>
#include <string.h>

#define MAX 1001

int max2(int a, int b) { return a > b ? a : b; }

/* LCS length of two null-terminated strings */
int lcs(char *text1, char *text2) {
    int dp[MAX][MAX];
    int m = (int)strlen(text1), n = (int)strlen(text2);
    int i, j;

    for (i = 0; i <= m; i++) dp[i][0] = 0;
    for (j = 0; j <= n; j++) dp[0][j] = 0;

    for (i = 1; i <= m; i++) {
        for (j = 1; j <= n; j++) {
            if (text1[i - 1] == text2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max2(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
}

int main(void) {
    printf("%d\n", lcs("abcde", "ace")); /* 3 */
    printf("%d\n", lcs("abc", "abc")); /* 3 */
    printf("%d\n", lcs("abc", "def")); /* 0 */
    return 0;
}

LCS Output & Trace

Program output (all three languages)

Language Input Output One LCS
Python"abcde", "ace"3"ace"
Python"abc", "abc"3"abc" (full string)
Python"abc", "def"0No common char
Java / Csame tests3, 3, 0Identical logic

DP table trace — text1 = "abcde", text2 = "ace"

dp[i][j] "" a c e
""0000
a0111
b0111
c0122
d0122
e0123
Reading the table: Rows = prefix of text1, columns = prefix of text2. Answer at bottom-right = 3. Matches on diagonal (a, c, e) increase the count by 1.

Longest Palindromic Subsequence

Problem (LeetCode 516): Find the length of the longest subsequence of s that reads the same forward and backward.

State: dp[i][j] = LPS length in substring s[i..j].

Recurrence:

  • Base: dp[i][i] = 1 (single character)
  • If s[i] == s[j]: dp[i][j] = dp[i+1][j−1] + 2
  • Else: dp[i][j] = max(dp[i+1][j], dp[i][j−1])

Fill i from bottom to top so inner subproblems are ready.

LPS Code (C, Java, Python)

Python

def longest_palindrome_subseq(s):
    """LeetCode 516 — longest palindromic subsequence length."""
    n = len(s)
    dp = [[0] * n for _ in range(n)]

    for i in range(n - 1, -1, -1):
        dp[i][i] = 1
        for j in range(i + 1, n):
            if s[i] == s[j]:
                dp[i][j] = dp[i + 1][j - 1] + 2   # pair outer chars
            else:
                dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])

    return dp[0][n - 1]


if __name__ == "__main__":
    for s in ["bbbab", "cbbd", "a"]:
        print(f"LPS('{s}') = {longest_palindrome_subseq(s)}")

Java

public class LongestPalindromeSubseq {

    public static int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];

        for (int i = n - 1; i >= 0; i--) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][n - 1];
    }

    public static void main(String[] args) {
        System.out.println(longestPalindromeSubseq("bbbab")); // 4
        System.out.println(longestPalindromeSubseq("cbbd"));  // 2
        System.out.println(longestPalindromeSubseq("a"));    // 1
    }
}

C

#include <stdio.h>
#include <string.h>

#define MAX 1001

int max2(int a, int b) { return a > b ? a : b; }

int lps(char *s) {
    int dp[MAX][MAX];
    int n = (int)strlen(s);
    int i, j;

    for (i = n - 1; i >= 0; i--) {
        dp[i][i] = 1;
        for (j = i + 1; j < n; j++) {
            if (s[i] == s[j]) {
                dp[i][j] = dp[i + 1][j - 1] + 2;
            } else {
                dp[i][j] = max2(dp[i + 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[0][n - 1];
}

int main(void) {
    printf("%d\n", lps("bbbab")); /* 4 */
    printf("%d\n", lps("cbbd"));  /* 2 */
    printf("%d\n", lps("a"));     /* 1 */
    return 0;
}

LPS Output & Trace

Program output

Language Input Output One palindromic subseq
Python"bbbab"4"bbbb"
Python"cbbd"2"bb"
Python"a"1"a"
Java / Csame tests4, 2, 1Same results

Key cells — s = "bbbab" (indices 0..4)

Subproblem s[i..j] dp[i][j] Why
[4..4] "b"1Single char base
[3..4] "ab"1a ≠ b → max of [4..4], [3..3]
[1..3] "bba"2Pair b's at ends → "bb"
[0..4] "bbbab"4Best: "bbbb" via outer b's + inner "bb"

Related Subsequence Problems

Problem Key insight Formula / twist LeetCode
Delete operation for two stringsKeep LCS, delete the restAnswer = len(s1)+len(s2) − 2·LCS583
Shortest common supersequenceCover both stringslen(s1)+len(s2) − LCS1092
Distinct subsequencesCount matchingsOn match: dp[i−1][j−1] + dp[i−1][j]115
Longest common substringContiguous onlyOn mismatch: dp[i][j]=0Custom
Edit distanceInsert/delete/replaceThree-way min on mismatchEdit distance tutorial

See also longest increasing subsequence for subsequence on numeric arrays (1D, not 2D string DP).

Space Optimization

LCS depends only on the previous row — compress to O(n) space:

2D: dp[i][j] from row i-1 and column j-1
1D: prev[j] and curr[j]; iterate i = 1..m, j = 1..n
     match: curr[j] = prev[j-1] + 1
     else:   curr[j] = max(prev[j], curr[j-1])

LPS: interval DP needs full table (or O(n²) with careful ordering)
     — harder to optimize to O(n) without losing reconstruction

More Examples

Example 1: LCS → min deletions (LeetCode 583)

word1word2LCSMin deletionsCalc
seaeat2 ("ea")2(3−2)+(3−2) = 1+1 deletions

Min deletions to make equal = (m − LCS) + (n − LCS) = m + n − 2·LCS.

Example 2: No common subsequence

text1text2LCS lengthLPS of "abcba"
abcdef05 ("abcba")

Example 3: Complexity summary

ProblemTimeSpaceNotes
LCSO(m × n)O(m × n) / O(n)Classic 2D string DP
LPSO(n²)O(n²)Interval DP on one string
Distinct subsequencesO(m × n)O(n) possibleCounting variant
Brute forceO(2^m)O(m)Generate all subsequences — avoid

Approach Comparison

Approach Time Space When to use
2D bottom-up DPO(m × n)O(m × n)Whiteboard; reconstruct subsequence
1D rolling array (LCS)O(m × n)O(n)Space-optimized final code
Top-down memoO(m × n)O(m × n) + stackQuick recursive draft
Interval DP (LPS)O(n²)O(n²)Substrings of one string

Real-Life Applications

Domain Real scenario Subsequence mapping Variant
BioinformaticsDNA/protein sequence alignmentFind conserved regions across genomesLCS / edit distance
Version controlGit diff / merge — common lines keptTwo file versions → longest unchanged subsequenceLCS
Plagiarism detectionSimilar document passagesLong common subsequence of token streamsLCS / substring
Spell check / NLPMeasure similarity of sentencesWord-level LCS as similarity scoreLCS
Data compressionPalindrome structure in repeated dataLongest palindromic patternsLPS
Screen readersPalindrome detection in tokensSymmetric text patternsLPS
Database syncMinimal edits to reconcile two logsLCS → what to keep; rest insert/deleteDelete ops / SCS
Competitive programmingCount ways string T appears in SDistinct subsequences DPLeetCode 115

Interview Tips

  1. Clarify: subsequence (gaps OK) vs substring (contiguous) vs palindrome constraint.
  2. Define dp[i][j] in words before writing loops — prefix vs substring range.
  3. Draw the small LCS table for "ace" vs "abcde" on the whiteboard.
  4. For LPS: fill i from n−1 down to 0; base case dp[i][i]=1.
  5. Follow-ups: reconstruct LCS (backtrack from dp[m][n]); O(n) space for LCS.
  6. Connect delete-operation (583) to LCS: deletions = m + n − 2·LCS.
  7. Do not confuse LCS with edit distance — replace costs extra.
  8. State complexity O(m × n) time explicitly.

Key Takeaway

LCS: on match take diagonal + 1; on mismatch take max of top/left. LPS: on match take inner interval + 2; fill backwards over substrings. Both are 2D string DP — master the table trace and you unlock delete-op, supersequence, and distinct-subsequence variants.

Next up: Edit distance · Longest increasing subsequence · Word break