392. Is Subsequence
Problem Description
You are given two strings s
and t
. Your task is to determine if s
is a subsequence of t
.
A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements. In other words, you can obtain a subsequence by removing characters from the original string while keeping the relative positions of the remaining characters unchanged.
For example:
"ace"
is a subsequence of"abcde"
because you can delete'b'
and'd'
to get"ace"
"aec"
is not a subsequence of"abcde"
because although all characters exist, they don't appear in the correct order
The function should return true
if s
is a subsequence of t
, and false
otherwise.
The solution uses a two-pointer approach where pointer i
tracks the position in string s
and pointer j
tracks the position in string t
. As we iterate through both strings:
- When characters at positions
i
andj
match, we move both pointers forward - When they don't match, we only move pointer
j
forward to continue searching int
- If we successfully match all characters in
s
(wheni
reaches the end ofs
), thens
is a subsequence oft
Intuition
To check if s
is a subsequence of t
, we need to find all characters of s
in t
while maintaining their original order. Think of it like reading through a book (t
) and trying to find specific words (s
) in sequence - you can skip words in between, but the words you're looking for must appear in the correct order.
The key insight is that we need to traverse through string t
and try to match characters from string s
one by one. Since we must maintain the order of characters in s
, once we find a matching character, we should look for the next character of s
starting from the current position in t
, not from the beginning.
This naturally leads us to a greedy approach: whenever we find a character in t
that matches the current character we're looking for in s
, we immediately take it. We don't need to consider other occurrences of the same character later in t
because:
- Taking the first occurrence won't prevent us from finding subsequent characters of
s
- If a valid subsequence exists, using the earliest possible matches will always work
For example, checking if "abc"
is a subsequence of "ahbgdc"
:
- We look for
'a'
and find it at position 0 - We look for
'b'
starting from position 1 and find it at position 2 - We look for
'c'
starting from position 3 and find it at position 5 - All characters found in order →
"abc"
is a subsequence
This greedy scanning process can be efficiently implemented using two pointers: one to track our progress through s
(what we're looking for) and another to scan through t
(where we're looking).
Solution Approach
The solution implements a two-pointer technique to efficiently check if s
is a subsequence of t
.
Algorithm Steps:
-
Initialize two pointers:
i = 0
for strings
andj = 0
for stringt
-
Iterate through both strings simultaneously using a while loop with condition
i < len(s) and j < len(t)
:- Compare characters at current positions:
s[i]
andt[j]
- If they match (
s[i] == t[j]
), incrementi
to move to the next character ins
- Always increment
j
to move to the next character int
regardless of whether there's a match
- Compare characters at current positions:
-
After the loop ends, check if
i == len(s)
:- If true, all characters of
s
were found int
in order, sos
is a subsequence - If false, we couldn't find all characters of
s
int
, sos
is not a subsequence
- If true, all characters of
Why the algorithm works:
- Pointer
i
only advances when we find a matching character, ensuring we match all characters ofs
in sequence - Pointer
j
always advances, allowing us to scan through the entire stringt
- The loop terminates when either:
- We've matched all characters of
s
(success case:i == len(s)
) - We've scanned all of
t
but haven't matched all ofs
(failure case:j == len(t)
buti < len(s)
)
- We've matched all characters of
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the length of stringt
, as we traverset
at most once - Space Complexity:
O(1)
as we only use two pointers regardless of input size
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through checking if s = "ace"
is a subsequence of t = "abcde"
.
Initial Setup:
s = "ace"
(length 3)t = "abcde"
(length 5)i = 0
(pointer for s)j = 0
(pointer for t)
Step-by-step execution:
Iteration 1:
- Compare
s[0] = 'a'
witht[0] = 'a'
- They match! Increment
i
to 1 - Increment
j
to 1 - Continue since
i < 3
andj < 5
Iteration 2:
- Compare
s[1] = 'c'
witht[1] = 'b'
- No match. Keep
i
at 1 - Increment
j
to 2 - Continue since
i < 3
andj < 5
Iteration 3:
- Compare
s[1] = 'c'
witht[2] = 'c'
- They match! Increment
i
to 2 - Increment
j
to 3 - Continue since
i < 3
andj < 5
Iteration 4:
- Compare
s[2] = 'e'
witht[3] = 'd'
- No match. Keep
i
at 2 - Increment
j
to 4 - Continue since
i < 3
andj < 5
Iteration 5:
- Compare
s[2] = 'e'
witht[4] = 'e'
- They match! Increment
i
to 3 - Increment
j
to 5 - Loop ends since
j = 5
(reached end of t)
Final Check:
i = 3
which equalslen(s) = 3
- All characters of
s
were found in order - Return
true
- "ace" is a subsequence of "abcde"
The matched characters were at positions 0, 2, and 4 in string t
, maintaining the original order from string s
.
Solution Implementation
1class Solution:
2 def isSubsequence(self, s: str, t: str) -> bool:
3 """
4 Determines if string s is a subsequence of string t.
5 A subsequence is formed by deleting some (or no) characters
6 from t without changing the relative order of remaining characters.
7
8 Args:
9 s: The potential subsequence string
10 t: The target string to check against
11
12 Returns:
13 True if s is a subsequence of t, False otherwise
14 """
15 # Initialize two pointers: one for string s and one for string t
16 s_pointer = 0
17 t_pointer = 0
18
19 # Iterate through both strings simultaneously
20 while s_pointer < len(s) and t_pointer < len(t):
21 # If characters match, move the s_pointer forward
22 if s[s_pointer] == t[t_pointer]:
23 s_pointer += 1
24 # Always move the t_pointer forward to scan through t
25 t_pointer += 1
26
27 # If we've matched all characters in s, it's a subsequence
28 return s_pointer == len(s)
29
1class Solution {
2 /**
3 * Determines if string s is a subsequence of string t.
4 * A subsequence is formed by deleting some (can be none) characters
5 * from t without changing the relative order of remaining characters.
6 *
7 * @param s - The potential subsequence string
8 * @param t - The target string to check against
9 * @return true if s is a subsequence of t, false otherwise
10 */
11 public boolean isSubsequence(String s, String t) {
12 // Get lengths of both strings
13 int sLength = s.length();
14 int tLength = t.length();
15
16 // Initialize two pointers:
17 // sPointer - tracks current position in string s
18 // tPointer - tracks current position in string t
19 int sPointer = 0;
20 int tPointer = 0;
21
22 // Iterate through both strings simultaneously
23 while (sPointer < sLength && tPointer < tLength) {
24 // If characters match, move pointer in s forward
25 if (s.charAt(sPointer) == t.charAt(tPointer)) {
26 sPointer++;
27 }
28 // Always move pointer in t forward
29 tPointer++;
30 }
31
32 // If we've matched all characters in s, it's a subsequence
33 return sPointer == sLength;
34 }
35}
36
1class Solution {
2public:
3 bool isSubsequence(string s, string t) {
4 // Get the lengths of both strings
5 int sLength = s.size();
6 int tLength = t.size();
7
8 // Initialize two pointers:
9 // sIndex: tracks current position in string s (subsequence)
10 // tIndex: tracks current position in string t (main string)
11 int sIndex = 0;
12 int tIndex = 0;
13
14 // Iterate through string t while both pointers are within bounds
15 for (; sIndex < sLength && tIndex < tLength; ++tIndex) {
16 // If characters match, move pointer in string s forward
17 if (s[sIndex] == t[tIndex]) {
18 ++sIndex;
19 }
20 // Otherwise, continue to next character in t (tIndex increments in loop)
21 }
22
23 // If we've matched all characters in s, then s is a subsequence of t
24 return sIndex == sLength;
25 }
26};
27
1/**
2 * Determines if string s is a subsequence of string t.
3 * A subsequence is a sequence that can be derived from another sequence
4 * by deleting some or no elements without changing the order of the remaining elements.
5 *
6 * @param s - The potential subsequence string
7 * @param t - The string to check against
8 * @returns true if s is a subsequence of t, false otherwise
9 */
10function isSubsequence(s: string, t: string): boolean {
11 const sLength: number = s.length;
12 const tLength: number = t.length;
13
14 // Pointer for tracking position in string s
15 let sPointer: number = 0;
16
17 // Iterate through string t while we haven't matched all characters in s
18 for (let tPointer: number = 0; sPointer < sLength && tPointer < tLength; tPointer++) {
19 // If current characters match, advance the pointer in string s
20 if (s[sPointer] === t[tPointer]) {
21 sPointer++;
22 }
23 }
24
25 // If we've matched all characters in s, it's a subsequence
26 return sPointer === sLength;
27}
28
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of string t
. In the worst case, we need to traverse the entire string t
once. Even though we also traverse string s
, we never go beyond the length of t
since both pointers move forward together and the loop terminates when either i
reaches the end of s
or j
reaches the end of t
. The reference answer states O(m + n)
, but since we traverse at most n
characters (length of t
), and i
can increment at most m
times (length of s
), where m ≤ n
for a valid subsequence, the complexity simplifies to O(n)
.
Space Complexity: O(1)
. The algorithm uses only two pointer variables i
and j
regardless of the input size, requiring constant extra space.
Common Pitfalls
1. Not Handling Empty Strings Correctly
A common mistake is not considering edge cases where one or both strings are empty. While the provided solution handles these cases correctly, developers might overcomplicate the logic with unnecessary special case checks.
Pitfall Example:
def isSubsequence(self, s: str, t: str) -> bool:
# Unnecessary edge case handling that complicates the code
if not s and not t:
return True
if not t:
return False
if not s:
return True
# ... rest of the logic
Why the Original Solution Works: The two-pointer approach naturally handles empty strings:
- If
s
is empty,s_pointer
starts at 0 andlen(s)
is 0, so the while loop never executes and the function returnsTrue
(empty string is a subsequence of any string) - If
t
is empty buts
is not, the while loop never executes ands_pointer
remains 0, returningFalse
2. Incorrect Pointer Movement Logic
A frequent error is moving both pointers only when characters match, forgetting to advance the t_pointer
when characters don't match.
Incorrect Implementation:
def isSubsequence(self, s: str, t: str) -> bool:
s_pointer = 0
t_pointer = 0
while s_pointer < len(s) and t_pointer < len(t):
if s[s_pointer] == t[t_pointer]:
s_pointer += 1
t_pointer += 1 # WRONG: Only moving t_pointer here
# Missing: t_pointer should advance even when no match
return s_pointer == len(s)
Solution: Always advance t_pointer
regardless of whether there's a match:
while s_pointer < len(s) and t_pointer < len(t):
if s[s_pointer] == t[t_pointer]:
s_pointer += 1
t_pointer += 1 # Always advance t_pointer
3. Using Incorrect Loop Termination Condition
Some implementations use or
instead of and
in the while loop condition, which causes premature termination.
Incorrect Implementation:
def isSubsequence(self, s: str, t: str) -> bool:
s_pointer = 0
t_pointer = 0
# WRONG: Using 'or' causes loop to continue even after one string is exhausted
while s_pointer < len(s) or t_pointer < len(t):
if s[s_pointer] == t[t_pointer]: # Can cause IndexError
s_pointer += 1
t_pointer += 1
return s_pointer == len(s)
Why This Fails: Using or
allows the loop to continue when one pointer has reached the end, potentially causing an IndexError
when trying to access characters beyond the string length.
Solution: Use and
to ensure both pointers are within bounds:
while s_pointer < len(s) and t_pointer < len(t):
# Safe to access both s[s_pointer] and t[t_pointer]
4. Attempting to Optimize with Early Return Inside Loop
While adding an early return when all characters are matched seems like an optimization, it can make the code less clean without providing significant performance benefits.
Overly Complex Version:
def isSubsequence(self, s: str, t: str) -> bool:
s_pointer = 0
t_pointer = 0
while s_pointer < len(s) and t_pointer < len(t):
if s[s_pointer] == t[t_pointer]:
s_pointer += 1
if s_pointer == len(s): # Unnecessary early return
return True
t_pointer += 1
return s_pointer == len(s)
Why Original is Better: The original solution is cleaner and the performance difference is negligible since we still need to check the condition either way. The single return statement at the end makes the code more readable and maintainable.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!