Is Subsequence
Problem
s and t, decide whether s can be
obtained from t by deleting some (possibly zero) characters without
reordering the rest — i.e. whether s is a subsequence of t.s="abc", t="ahbgdc"Outputtrues="axc", t="ahbgdc"Outputfalse0 ≤ |s| ≤ 100 · 0 ≤ |t| ≤ 104 · lowercase letters · Follow-up: billions of s queries against one t.
Try it yourself
class Solution {
public boolean isSubsequence(String s, String t) {
}
}
s character in t leaves the most room for the remaining characters.Instinct Reading, comparing or rebuilding text character by character? Walk indices on a char[] and build output with a StringBuilder, never with + in a loop.
Memory hack Chars in an array, pointers on the ends, builder at the back - strings are just arrays wearing a costume.
Open the String Manipulation guide →Approaches
Brute ForceLCS Dynamic Programming
O(n·m)Space O(n·m)s is a subsequence of t iff
LCS(s, t) == |s|. Fill the classic table: on a match extend the diagonal,
otherwise take the max of skipping a character from either string.
public boolean isSubsequence(String s, String t) { int n = s.length(), m = t.length(); int[][] dp = new int[n + 1][m + 1]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s.charAt(i - 1) == t.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[n][m] == n; }
Correct but heavy — solves a harder problem than needed.
BetterTwo Pointers (Greedy Scan)
O(n + m)Space O(1)Walk t with j; whenever t[j] equals
s[i], advance i. If i reaches
|s|, every character was matched in order. Best possible for a
single query.
public boolean isSubsequence(String s, String t) { int i = 0, j = 0; while (i < s.length() && j < t.length()) { if (s.charAt(i) == t.charAt(j)) i++; j++; } return i == s.length(); }
Dry runs="abc", t="ahbgdc"
- j=0ajhbgdcmatch a → i=1
- j=1ahjbgdch≠b → skip
- j=2ahbjgdcmatch b → i=2
- j=3,4ahbgdjcg, d ≠ c → skip
- j=5ahbgdcjmatch c, i=3 ✔
Optimal for one query, but rescans all of t for every new s.
OptimalIndex Lists + Binary Search (Follow-up)
O(m) once + O(n log m)/querySpace O(m)Preprocess t once: for each letter, the sorted list of its positions.
For each s character, binary-search the first position after the
previous match. Per query cost no longer depends linearly on |t|.
public boolean isSubsequence(String s, String t) { List<List<Integer>> pos = new ArrayList<>(); // build once per t, reuse for (int c = 0; c < 26; c++) pos.add(new ArrayList<>()); for (int j = 0; j < t.length(); j++) pos.get(t.charAt(j) - 'a').add(j); int prev = -1; for (char ch : s.toCharArray()) { List<Integer> list = pos.get(ch - 'a'); int k = Collections.binarySearch(list, prev + 1); if (k < 0) k = -k - 1; // insertion point = first index > prev if (k == list.size()) return false; prev = list.get(k); } return true; }
Best when t is fixed and many s arrive; overkill for one query.
next[j][c] table (next index of letter c at or after j) makes each
query O(n) at the cost of O(26·m) memory.At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | LCS Dynamic Programming | O(n·m) | O(n·m) |
| Better | Two Pointers (Greedy Scan) | O(n + m) | O(1) |
| Optimal | Index Lists + Binary Search (Follow-up) | O(m) once + O(n log m)/query | O(m) |
- Advancing the
spointer on a mismatch — onlyt's pointer always moves. - Forgetting that an empty
sis always a subsequence (return true). - Confusing subsequence with substring — characters need not be contiguous.
- Justify the greedy: earliest match never hurts later matches.
- Bring up the follow-up yourself and sketch the binary-search preprocessing.
- Note the DP view links to LCS / Edit Distance if asked to generalise.