2486. Append Characters to String to Make Subsequence - Explanation

Problem Link

Description

You are given two strings s and t consisting of only lowercase English letters.

Return the minimum number of characters that need to be appended to the end of s so that t becomes a subsequence of s.

A subsequence is a string that can be derived from another string by deleting some or no characters without changing the order of the remaining characters.

Example 1:

Input: s = "coaching", t = "coding"

Output: 4

Explanation: Append the characters "ding" to the end of s so that s = "coachingding".
Now, t is a subsequence of s (coachingding).
It can be shown that appending any 3 characters to the end of s will never make t a subsequence.

Example 2:

Input: s = "abcde", t = "a"

Output: 0

Explanation: t is already a subsequence of s ("abcde").

Example 3:

Input: s = "z", t = "abcde"

Output: 5

Explanation: Append the characters "abcde" to the end of s so that s = "zabcde".
Now, t is a subsequence of s (zabcde).
It can be shown that appending any 4 characters to the end of s will never make t a subsequence.

Constraints:

  • 1 <= s.length, t.length <= 100,000
  • s and t consist of lowercase English letters.


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Two Pointers - Used to traverse both strings simultaneously while matching characters
  • Subsequences - Understanding what makes one string a subsequence of another
  • Greedy Algorithms - Recognizing that matching characters from left to right is optimal

1. Two Pointers

Intuition

To make t a subsequence of s by appending characters, we first need to figure out how much of t is already a subsequence of s. We can greedily match characters from t in s from left to right. Once we know how many characters of t we can match, the remaining characters must be appended.

The greedy approach works because matching earlier characters in s never hurts. If a character from t appears multiple times in s, taking the first occurrence leaves more room for matching subsequent characters.

Algorithm

  1. Use two pointers: i for string s and j for string t.
  2. Iterate through s. Whenever s[i] matches t[j], move both pointers forward. Otherwise, only advance i.
  3. After scanning s, j represents how many characters of t are already matched.
  4. Return len(t) - j, the number of characters that need to be appended to make the match.
class Solution:
    def appendCharacters(self, s: str, t: str) -> int:
        i, j = 0, 0

        while i < len(s) and j < len(t):
            if s[i] == t[j]:
                i += 1
                j += 1
            else:
                i += 1
        return len(t) - j

Time & Space Complexity

  • Time complexity: O(n+m)O(n + m)
  • Space complexity: O(1)O(1)

Where nn and mm are the lengths of the strings ss and tt, respectively.


2. Index Jumping

Intuition

The two-pointer approach scans s character by character, which can be slow if s is long and the characters we need are sparse. We can speed this up by precomputing where each character appears in s. For any position, we can instantly jump to the next occurrence of the character we need.

We build a table where store[i][c] tells us the nearest index at or after position i where character c appears. This lets us skip over irrelevant characters in constant time.

Algorithm

  1. Build a 2D array store where store[i][c] holds the first occurrence of character c at or after index i in s.
  2. Fill this table by iterating backward through s. Each position inherits the next position's data, then updates the current character's entry.
  3. Use two pointers i and j for s and t. For each character in t, use store to jump directly to its next occurrence in s.
  4. If a character from t cannot be found (returns sentinel value), stop matching.
  5. Return len(t) - j.
class Solution:
    def appendCharacters(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        store = [[n + 1] * 26 for _ in range(n)]
        store[n - 1][ord(s[n - 1]) - ord('a')] = n - 1

        for i in range(n - 2, -1, -1):
            store[i] = store[i + 1][:]
            store[i][ord(s[i]) - ord('a')] = i

        i, j = 0, 0
        while i < n and j < m:
            if store[i][ord(t[j]) - ord('a')] == n + 1:
                break

            i = store[i][ord(t[j]) - ord('a')] + 1
            j += 1

        return m - j

Time & Space Complexity

  • Time complexity: O(n+m)O(n + m)
  • Space complexity: O(n)O(n)

Where nn and mm are the lengths of the strings ss and tt, respectively.


Common Pitfalls

Returning the Wrong Value After Matching

The result should be the number of unmatched characters in t, not the number of matched characters. After the loop, j represents how many characters were matched, so you need to return len(t) - j, not j.

# Wrong: returning matched count
return j  # Returns how many matched, not how many to append

# Correct: return remaining characters
return len(t) - j

Advancing Both Pointers on Mismatch

When characters do not match, only the pointer for s should advance. The pointer for t should stay in place waiting for its character to appear later in s. Advancing both pointers skips characters in t that might match later.

# Wrong: advancing both pointers
if s[i] != t[j]:
    i += 1
    j += 1  # Skips a character in t that might match later

# Correct: only advance s pointer
if s[i] != t[j]:
    i += 1