Last Updated: March 20, 2026
We need to check whether every character of s appears in t, in the same order, but not necessarily consecutively. We can skip characters in t, but we can't rearrange them.
Think of it like scanning through a book (t) looking for specific letters in a specific order (s). You read the book from left to right, and every time you spot the next letter you need, you mark it off your list. If you reach the end of your list before the book runs out, you've found your subsequence. If the book ends first, you haven't.
The follow-up is where this problem gets interesting. Checking one string against t is straightforward. But what if you need to check a billion strings against the same t? A linear scan for each one adds up fast. That's where preprocessing t pays off.
0 <= s.length <= 100 → s is very short. Even an O(n^2) approach on s alone would be trivial.0 <= t.length <= 10^4 → t can be moderately long. O(m) per query is fine for a single check, but if there are billions of queries, we want to minimize work per query.lowercase English letters only → Just 26 possible characters. This is a strong hint for the follow-up: we can build a lookup structure indexed by character.s can be empty → An empty string is a subsequence of everything. Don't forget this edge case.The most natural approach: walk through both strings simultaneously. Use one pointer for s and one for t. Every time the characters match, advance the pointer on s. Always advance the pointer on t. If we match all characters of s before running out of t, then s is a subsequence.
When we find a matching character in t, there's no benefit to skipping it and hoping for a "better" match later. The characters in s must appear in order, and taking the earliest available match in t leaves the most room for matching subsequent characters. This is a classic greedy argument: the earliest match is always at least as good as any later match.
i = 0 for s, j = 0 for t.s[i] == t[j], we've matched a character. Advance both i and j.j (skip this character in t).i == s.length, all characters of s were matched. Return true. Otherwise, return false.s and m = length of t. We traverse each string at most once. The pointer j moves through every character of t, and i moves at most n times.For a single query, this is already optimal. But the follow-up asks what happens when we have billions of queries against the same t. Every query re-scans t from scratch. Can we preprocess t once so each query skips the linear scan?
The follow-up asks: what if we check many strings against the same t? The two-pointer approach scans t from scratch for every query. If t never changes, we should preprocess it once and reuse that information.
Here's the idea: for each character in the alphabet, store the sorted list of indices where that character appears in t. Then, for each character in s, instead of scanning t linearly, we binary search for the next valid position.
For example, if t = "ahbgdc", we'd build this index map:
To check if s = "abc" is a subsequence, we start at position -1 (before the string). For 'a', binary search for the smallest index > -1 in [0]. That gives us 0. For 'b', search for the smallest index > 0 in [2]. That gives us 2. For 'c', search for the smallest index > 2 in [5]. That gives us 5. We found valid positions for all characters, so it's a subsequence.
t.s:prevIndex = -1 (the position of the last matched character).c in s:c. If the list doesn't exist, return false.prevIndex.false.prevIndex to this index.true.t. For each character in s, we do a binary search over at most m indices. With n characters in s, each query is O(n log m).t exactly once.Each query still takes O(n log m) because of the binary search. What if we precomputed the answer for every (position, character) pair, so each lookup became O(1)?
We can precompute a "jump table" for t: for every position j and every character c, store the index of the next occurrence of c at or after position j. This turns each character lookup into an O(1) table access.
The table is built by scanning t from right to left. At position j, the next occurrence of t[j] is j itself. For all other characters, the next occurrence is the same as what it was at position j + 1. This right-to-left scan fills the entire table in O(26 * m) time.
Once the table is built, checking a query is simple: start at position 0 in t, look up the next occurrence of s[0], jump there, then look up the next occurrence of s[1] from the new position, and so on. Each lookup is O(1), so the total per query is O(n).
next of size (m + 1) x 26.j = m - 1 down to 0:j + 1 into row j.next[j][t[j] - 'a'] = j (this character is available at position j).s:j = 0.c in s:next[j][c - 'a'].false (character not available).j = next[j][c - 'a'] + 1 (move past this matched character).true.t once and fills 26 entries per position. Each query processes n characters with O(1) lookups per character.