[Easy] 392. Is Subsequence
Given two strings s and t, return true if s is a subsequence of t, or false otherwise.
A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (i.e., "ace" is a subsequence of "abcde" while "aec" is not).
Thinking Process
Given two strings s and t, return true if s is a subsequence of t, or false otherwise.
A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (i.e., "ace" is a subsequence of "abcde" while "aec" is not).
- Define state: what subproblem does
dp[i](ordp[i][j]) represent? - Recurrence: how does the answer build from smaller indices?
- Base cases first; optimize space if only prior row/layer is needed.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| 1D DP (this problem) | O(n) | O(n) or O(1) | Linear recurrence |
| 2D DP | O(nm) | O(nm) or O(n) | Grid or two-sequence problems |
| State machine DP | O(n) | O(1) | Buy/sell, hold/not-hold states |
| Memoization (top-down) | Same as DP | O(n) | Recursive + cache |
Examples
Example 1:
Input: s = "abc", t = "ahbgdc"
Output: true
Explanation: "abc" is a subsequence of "ahbgdc" (characters at positions 0, 2, 5).
Example 2:
Input: s = "axc", t = "ahbgdc"
Output: false
Explanation: "axc" is not a subsequence of "ahbgdc" because 'x' is not found in "ahbgdc".
Constraints
0 <= s.length <= 1000 <= t.length <= 10^4sandtconsist only of lowercase English letters.
Algorithm Breakdown
Why Two Pointers Work
The two-pointer approach is optimal because:
- Order Preservation: We only advance
iwhen we find a match, ensuring order - Greedy Choice: Always match the first occurrence in
t(greedy) - Linear Time: Single pass through both strings
- Optimal: O(n + m) time complexity
Pointer Movement Logic
- When characters match (
s[i] == t[j]):- Advance
i(found character ins) - Advance
j(move past matched character int) - Then advance
jagain (check next character int)
- Advance
- When characters don’t match (
s[i] != t[j]):- Keep
iunchanged (still looking for this character) - Advance
j(skip this character int)
- Keep
Note: The code increments j twice when there’s a match (once in the if block, once after). This is equivalent to:
if(s[i] == t[j]) {
i++;
}
j++; // Always advance j
Subsequence Property
A subsequence maintains the relative order of characters:
"ace"is a subsequence of"abcde"(positions 0, 2, 4)"aec"is NOT a subsequence of"abcde"(can’t get ‘e’ before ‘c’)
Time & Space Complexity
- Time Complexity: O(m) where m is the length of
t- We iterate through
tat most once - Pointer
ican only advance up ton(length ofs) - In worst case, we scan all of
t
- We iterate through
- Space Complexity: O(1)
- Only using a few variables
- No additional data structures
Key Points
- Two Pointers: Efficient matching technique
- Greedy Matching: Match first occurrence in
t - Order Preservation: Characters must appear in same order
- Simple Check: All characters matched if
i == N - Edge Case: Empty string
sis always a subsequence
Common Mistakes
- Empty
s:s = ""→ returntrue(empty is subsequence of any string) - Empty
t:s = "a",t = ""→ returnfalse - Same strings:
s = "abc",t = "abc"→ returntrue - Single character:
s = "a",t = "abc"→ returntrue - No match:
s = "x",t = "abc"→ returnfalse -
Repeated characters:
s = "aa",t = "abac"→ returntrue - Wrong pointer logic: Not advancing
jwhen no match - Order violation: Matching characters out of order
- Off-by-one: Wrong loop condition or index checking
- Empty string: Forgetting that empty string is always subsequence
- Not checking all characters: Returning early before checking all of
s
Related Problems
- 524. Longest Word in Dictionary through Deleting - Find longest subsequence
- 792. Number of Matching Subsequences - Count subsequences
- 1143. Longest Common Subsequence - Find LCS (DP)
- 727. Minimum Window Subsequence - Find minimum window containing subsequence
Follow-Up: Multiple Queries
If we need to check many strings s against the same t, we can optimize:
// Preprocess t to store character positions
unordered_map<char, vector<int>> charPositions;
for(int i = 0; i < t.length(); i++) {
charPositions[t[i]].push_back(i);
}
// For each query s, use binary search
bool isSubsequence(string s, unordered_map<char, vector<int>>& pos) {
int prev = -1;
for(char c : s) {
auto it = upper_bound(pos[c].begin(), pos[c].end(), prev);
if(it == pos[c].end()) return false;
prev = *it;
}
return true;
}
Time: O(m + n log m) per query (better when many queries)
Tags
String, Two Pointers, Greedy, Dynamic Programming, Easy
Key Takeaways
- Define state: what subproblem does
dp[i](ordp[i][j]) represent? - Recurrence: how does the answer build from smaller indices?
- Base cases first; optimize space if only prior row/layer is needed.
References
- LC 392: Is Subsequence on LeetCode
- LeetCode Discuss — LC 392: Is Subsequence
- LeetCode Editorial (may require premium)