Is one string a subsequence of another
You're given two strings s and t of lower-case letters. Return
true if you can get s by deleting some of the letters of t (or none of them)
and keeping the rest in the order they're in, and false if you can't.
Examples
| Input | Output | Why |
|---|---|---|
s = "abc", t = "ahbgdc" | true | If you delete the h, the g and the d you're left with abc. |
s = "axc", t = "ahbgdc" | false | There's no x anywhere in t so it can't work. |
s = "ba", t = "ab" | false | Both letters are there but they're the wrong way round, and you're not allowed to reorder. |
Constraints
- 0 ≤ s.length ≤ 100
- 0 ≤ t.length ≤ 10⁴
- s and t hold lower-case letters only
Hints
Hint 1
Say the first letter of s turns up several times in t. Which one of those would you want to match it to?
Hint 2
The earliest one, because that leaves the most of t for the rest of s and there's nothing a later match could do that the earlier one can't.
Hint 3
So you keep i on s and j on t and move j along every time, and you only move i when s[i] = t[j]. If i gets to the end of s the answer's true.
Where people lose marks
- Checking only that every letter of s turns up somewhere in t. That ignores the order, and ba against ab is the case I'd test it with.
- Forgetting that the empty string is a subsequence of everything, including another empty string.
- Reaching for the longest common subsequence DP, which does get the right answer but it's O(|s| × |t|) when one pass is enough.
Dry run on "abc","ahbgdc"
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 8sa0ib1c2ta0jh1b2g3d4c5i0j0
i points at the next letter of s you still need, and j walks along t.
-
Step 2 / 8sa0ib1c2ta0jh1b2g3d4c5i1j0
t[0] is a, which is the letter you need, so you match it here and i moves on.
-
Step 3 / 8sa0b1ic2ta0h1jb2g3d4c5i1j1
t[1] is h and you're still looking for b, so it gets skipped.
-
Step 4 / 8sa0b1ic2ta0h1b2jg3d4c5i2j2
There's the b at t[2]. That's 2 of 3 matched.
-
Step 5 / 8sa0b1c2ita0h1b2g3jd4c5i2j3
t[3] is g and you're still looking for c, so it gets skipped.
-
Step 6 / 8sa0b1c2ita0h1b2g3d4jc5i2j4
No c at t[4] either (it's a d), so j keeps going.
-
Step 7 / 8sa0b1c2ita0h1b2g3d4c5ji3j5
There's the c at t[5]. That's 3 of 3 matched.
-
Step 8 / 8sa0b1c2ta0h1b2g3d4c5resulttrue
Every letter of s was matched in order, so it is a subsequence.
How to think about it
The thing I'd want to convince myself of first is that matching greedily is safe. Say s[0]
turns up at a few places in t. If there's a way to match the whole of s that uses one of
the later places for s[0], then you can move that match back to the earliest place and nothing else
has to change, because everything after it is still after it. So you can always take the earliest match.
That gives you two pointers going the same way, i on s and j on
t. Every step moves j on, and when s[i] = t[j] it moves i on as
well. If i reaches the end of s then every letter was matched in order and you return
true, and if t runs out first you return false. That's one pass over t, so it's O(|t|).
Complexity. Time O(|s| + |t|) · Space O(1)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def is_subsequence(s, t):
i = 0
for c in t: # j walks t; i only moves on a match
if i < len(s) and s[i] == c:
i += 1
return i == len(s)#include <string>
bool solve(const std::string& s, const std::string& t) {
size_t i = 0;
for (size_t j = 0; j < t.size() && i < s.size(); ++j) // j walks t; i only moves on a match
if (s[i] == t[j]) ++i;
return i == s.size();
}class Solution {
public boolean isSubsequence(String s, String t) {
int i = 0;
for (int j = 0; j < t.length() && i < s.length(); j++) // j walks t; i only moves on a match
if (s.charAt(i) == t.charAt(j)) i++;
return i == s.length();
}
}function isSubsequence(s, t) {
let i = 0;
for (let j = 0; j < t.length && i < s.length; j++) // j walks t; i only moves on a match
if (s[i] === t[j]) i++;
return i === s.length;
}fn solve(s: &str, t: &str) -> bool {
let s = s.as_bytes();
let mut i = 0;
for &c in t.as_bytes() { // j walks t; i only moves on a match
if i < s.len() && s[i] == c { i += 1; }
}
i == s.len()
}solve :: String -> String -> Bool
solve [] _ = True
solve _ [] = False
solve (x : xs) (y : ys)
| x == y = solve xs ys -- match it here, at the earliest place
| otherwise = solve (x : xs) ys -- skip this letter of t
Variants to try
- If you're asked about lots of different s against the same long t, you'd store the list of positions for each letter of t and binary search for the next position after j, so each s costs O(|s| log |t|).
- If you're asked how many times s appears as a subsequence of t, greedy isn't enough any more and it turns into a DP over the two strings.
Next problems
Move the zeros to the end
One pointer reads every element, the other marks where the next non-zero belongs. The order of the non-zero values is kept.
Open the problem → Medium15 minKeep each value at most twice
A reader and a writer, and one comparison: a value may be written only if it differs from the value written two places back.
Open the problem → Easy10 minMerge two sorted arrays
The step at the heart of merge sort: one pointer per array, and the smaller front value goes next.
Open the problem →Write a solution and run it against the real test table.
Keep going
Move the zeros to the end
One pointer reads every element, the other marks where the next non-zero belongs. The order of the non-zero values is kept.
Open the problem → Medium15 minKeep each value at most twice
A reader and a writer, and one comparison: a value may be written only if it differs from the value written two places back.
Open the problem → Easy10 minMerge two sorted arrays
The step at the heart of merge sort: one pointer per array, and the smaller front value goes next.
Open the problem → Two pointersThe pattern behind it
Two indices walking a sorted or paired structure, each step ruling out one candidate for good.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.