Editorial
Core insight
Is Subsequence · Two Pointers
Core Insight for Is Subsequence
The core insight for the LeetCode 392 Solution relies on the Greedy Choice Property. To determine if s is a subsequence of t, we want to find the first character of s in t, then the second character of s in the remaining part of t, and so on.
There is no advantage to skipping the first occurrence of a matching character in t. If s[0] matches t[5] and t[10], picking t[5] is strictly better or equal to picking t[10] because it leaves more characters available (t[6...] vs t[11...]) to match the remainder of s.
Therefore, we can iterate through t with a "fast" pointer that checks every character. Simultaneously, we maintain a "slow" pointer on s that only advances when the fast pointer finds a matching character. If the slow pointer reaches the end of s, we have found the full subsequence.
Visual Description:
Visualize two arrays aligned horizontally. A pointer fast starts at index 0 of t and moves one step to the right in every iteration. A second pointer slow starts at index 0 of s. As fast scans t, we compare the value at t[fast] with s[slow]. When they are identical, slow moves one step to the right. If they differ, slow remains stationary. The process concludes when slow moves past the last index of s (success) or fast exhausts t (failure).
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 392: Is Subsequence Solution & Explanation
Problem Overview
TL;DR: Use two pointers moving at different speeds to verify if all characters of the source string appear in the target string in the correct order.
The LeetCode 392 problem asks us to determine if a string s is a subsequence of another string t. A subsequence is formed by deleting zero or more characters from t without changing the relative order of the remaining characters. For example, "ace" is a subsequence of "abcde", but "aec" is not. This is a popular interview question that tests fundamental string manipulation and pointer management skills.
Brute Force Approach for Is Subsequence
A naive or brute force approach might attempt to generate all possible subsequences of the string t and check if string s matches any of them. Alternatively, a naive recursive solution might branch for every character in t, exploring two possibilities: either including the current character in the subsequence or excluding it.
Pseudo-code for Naive Recursion:
function check(s_index, t_index):
if s_index == length(s): return true
if t_index == length(t): return false
if s[s_index] == t[t_index]:
return check(s_index + 1, t_index + 1)
return check(s_index, t_index + 1)Complexity Analysis:
While the recursive logic above mimics the greedy approach, a true brute force that generates all subsequences would have a time complexity of , where is the length of t.
Why it fails:
The constraint for t.length is up to . An approach would result in a massive number of operations, far exceeding the time limit (Time Limit Exceeded). Even an unoptimized recursive solution without memoization can hit stack overflow limits or excessive overhead for large inputs.
Algorithm Strategy: Two Pointer Patterns
- Initialization: Create two integer pointers,
i(representing the slow pointer fors) andj(representing the fast pointer fort), both initialized to 0. - Traversal: Enter a loop that continues as long as
iis less than the length ofsandjis less than the length oft. - Comparison and Movement:
- Compare the character
s[i]witht[j]. - If
s[i] == t[j], it means we found the current required character. Incrementi(slow pointer) to look for the next character ofs. - Regardless of whether a match was found, always increment
j(fast pointer) to process the next character int.
- Compare the character
- Termination Condition: The loop breaks when we either find all characters of
sor run out of characters int. - Result: Check if
iequals the length ofs. If it does, all characters were found in order, so returntrue. Otherwise, returnfalse.
Execution Flow
Let s = "abc" and t = "ahbgdc".
- Start:
i = 0('a'),j = 0('a'). - Step 1: Compare
s[0]('a') witht[0]('a').- Match found.
- Increment
ito 1. - Increment
jto 1. - State:
ipoints to 'b',jpoints to 'h'.
- Step 2: Compare
s[1]('b') witht[1]('h').- No match.
istays at 1.- Increment
jto 2. - State:
ipoints to 'b',jpoints to 'b'.
- Step 3: Compare
s[1]('b') witht[2]('b').- Match found.
- Increment
ito 2. - Increment
jto 3. - State:
ipoints to 'c',jpoints to 'g'.
- Step 4: Compare
s[2]('c') witht[3]('g').- No match.
- Increment
jto 4.
- Step 5: Compare
s[2]('c') witht[4]('d').- No match.
- Increment
jto 5.
- Step 6: Compare
s[2]('c') witht[5]('c').- Match found.
- Increment
ito 3. - Increment
jto 6.
- End:
i(3) equalss.length(3). Returntrue.
Proof of Correctness
The correctness relies on the invariant that if s is a subsequence of t, there exists a set of indices in t such that . Our algorithm finds the smallest valid index , then the smallest valid index , and so on.
Suppose the optimal solution uses an index for where (the index found by our greedy algorithm). Since , using leaves a strictly larger suffix of t available for matching the remaining characters . Therefore, the greedy choice never eliminates a valid solution if one exists.
Pattern Reuse Notes
The Two Pointers - Fast & Slow pattern is versatile. While LeetCode 392 uses differential speeds for linear matching, the following problems use the relative speed of pointers to detect cycles or find specific nodes:
In these problems, the "Fast" pointer typically moves two steps for every one step of the "Slow" pointer, allowing them to meet if a cycle exists. In LeetCode 392, the speed difference is conditional (Slow moves on match, Fast moves always), but the principle of managing two independent traversal rates remains the same.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 392
1class Solution {
2public:
3 bool isSubsequence(string s, string t) {
4 int i = 0; // Pointer for s (Slow)
5 int j = 0; // Pointer for t (Fast)
6
7 while (i < s.length() && j < t.length()) {
8 if (s[i] == t[j]) {
9 i++; // Move slow pointer only on match
10 }
11 j++; // Move fast pointer always
12 }
13
14 return i == s.length();
15 }
16};Java Solution for LeetCode 392
1class Solution {
2 public boolean isSubsequence(String s, String t) {
3 int i = 0; // Pointer for s (Slow)
4 int j = 0; // Pointer for t (Fast)
5
6 // Iterate while both pointers are within bounds
7 while (i < s.length() && j < t.length()) {
8 if (s.charAt(i) == t.charAt(j)) {
9 i++;
10 }
11 j++;
12 }
13
14 return i == s.length();
15 }
16}Python Solution for LeetCode 392
1class Solution:
2 def isSubsequence(self, s: str, t: str) -> bool:
3 i, j = 0, 0
4 n, m = len(s), len(t)
5
6 while i < n and j < m:
7 if s[i] == t[j]:
8 i += 1
9 j += 1
10
11 return i == n