DSA Guide
Strings

Is Subsequence

Check whether one string can be formed by deleting characters from another, with two pointers

EasyTwo Pointers (same direction)
StringTwo PointersDynamic Programming
Problem #392

Problem Statement

Given two strings s and t, return true if s is a subsequence of t.

A subsequence is formed by deleting zero or more characters from a string without changing the order of the remaining ones.

Input:  s = "abc", t = "ahbgdc"
Output: true
Why:    a h b g d c
        ↑   ↑     ↑

Input:  s = "axc", t = "ahbgdc"
Output: false
Why:    'a' and 'c' appear, but there is no 'x'

Subsequence vs. substring

A substring is contiguous: "bgd" is a substring of "ahbgdc".

A subsequence need not be: "abc" is a subsequence of "ahbgdc" but not a substring. Order must be preserved in both, but only substrings require adjacency.

Intuition

The key insight

Walk t once with a pointer into s. Whenever the current character of t matches the character s is waiting for, advance s's pointer. Otherwise just move on through t.

If s's pointer reaches the end, every character was found in order → true.

The greedy part is subtle but important: always match at the earliest opportunity. If s[i] can be matched now, matching it now is never worse than waiting for a later occurrence — an earlier match leaves strictly more of t available for the remaining characters.

Real-world analogy

Proofreading a long document for a checklist of words that must appear in order. You read the document once, top to bottom, ticking items off as you encounter them. You never go back — and you tick an item the first time you see it, because saving it for later can only cost you.

Watch it run

s = "abc", t = "ahbgdc"
a
t
h
b
g
d
c
looking_foramatcheda
t[0] = 'a' matches what s needs → advance both pointers.
1 / 5
One pointer walks t continuously; the other advances only on a match.

Approach

Walk t

If s[i] == t[j], advance i. Always advance j.

Return whether i reached the end of s

If t runs out first with characters of s unmatched, the answer is false.

Solution

def is_subsequence(s: str, t: str) -> bool:
    """True if s can be formed by deleting characters from t."""
    i = 0   # index into s — the character we are looking for

    for char in t:
        if i < len(s) and s[i] == char:
            i += 1

    return i == len(s)

An explicit two-pointer form:

def is_subsequence_two_pointers(s: str, t: str) -> bool:
    i = j = 0

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

    return i == len(s)

The Follow-Up That Changes the Answer

LeetCode adds: what if there are billions of strings s₁, s₂, … to check against the same t?

The two-pointer solution costs O(|t|) per query, so a billion queries means a billion full scans of t.

Preprocess t into a position index

Build, once, a map from each character to the sorted list of positions where it appears in t:

t = "ahbgdc"
{a: [0], h: [1], b: [2], g: [3], d: [4], c: [5]}

Then for each s, track the last matched position and binary-search that character's list for the smallest position greater than it. Each query becomes O(|s| log |t|) instead of O(|t|).

from bisect import bisect_right
from collections import defaultdict


class SubsequenceChecker:
    """Preprocess t once, then answer many is_subsequence queries quickly."""

    def __init__(self, t: str) -> None:
        self._positions: defaultdict[str, list[int]] = defaultdict(list)
        for index, char in enumerate(t):
            self._positions[char].append(index)

    def check(self, s: str) -> bool:
        prev = -1
        for char in s:
            spots = self._positions.get(char)
            if not spots:
                return False
            # First position strictly greater than prev.
            k = bisect_right(spots, prev)
            if k == len(spots):
                return False
            prev = spots[k]
        return True

Complexity Analysis

Time Complexity

O(n + m)

Space Complexity

O(1)

  • Time — one pass over t, with n = |s| and m = |t|. Effectively O(m), since the pointer into s never outpaces it.
  • Space — two indices.

The preprocessing variant is O(m) setup and O(m) space, then O(n log m) per query — the right trade when queries are numerous.

Edge Cases

On this page