Is Subsequence
You get two strings, s and t. Return true if you can turn t into s by deleting some of its letters (possibly none) while the remaining letters keep their order, and false otherwise. For example, ace is a subsequence of abcde, but aec is not.
Function
- sstring
- the string to look for
- tstring
- the string to delete letters from
- Returnsboolean
- true if s can be read inside t in order, possibly with gaps
Constraints
1 ≤ s.length ≤ 3 × 1041 ≤ t.length ≤ 5 × 104sandtcontain only lowercase English letters.
Examples
- Input
- s = "ace"t = "abcde"
- Output
- true
- Explanation
- Delete
banddfromabcdeandaceis left, in the same order.
- Input
- s = "aec"t = "abcde"
- Output
- false
- Explanation
thas all three letters, but the onlycsits before the onlye. After you use theeat index 4, nocis left to its right.
- Input
- s = "moon"t = "monsoon"
- Output
- true
- Explanation
- Use the
mat index 0, theos at indexes 1 and 4, and thenat index 6 ofmonsoon. The letters in between are deleted.
Follow-up
If t stays fixed and you check a million strings s against it, how would you prepare t so each check is faster than reading all of t?
Hints
Open them one at a time. Each one gives away a little more.
Look at the first letter of
s. Which copy of it intshould you use?Use the earliest copy. Taking a later one can only leave less of
tfor the rest ofs, so the earliest choice is never worse.Keep one index into
sand one intot. Move throughtone letter at a time, advance the index intoson every match, and check at the end whether it reached the end ofs.
Solution
A subsequence may skip letters of t anywhere, which suggests trying many placements of s. You do not need to: matching each letter of s at the earliest place it can go is never worse than any other choice, so one left to right pass with two pointers decides it.
Dynamic programming over prefixes
Correct, but does not finish on the largest tests
Intuition
Let dp[i][j] say whether the first i letters of s fit inside the first j letters of t. They fit if they already fit without t[j-1], or if s[i-1] equals t[j-1] and the first i-1 letters fit inside t[:j-1]. The empty prefix fits everywhere, and row i reads only row i-1, so two rows of length m+1 are enough.
The table fills every cell: with s of 25,000 letters and t of 50,000, that is 1.25 × 10^9 cells.
Algorithm
- Create a row
prevofm+1values, alltrue: an emptysfits in every prefix oft. - For each
ifrom 1 ton, create a rowcurwithcur[0] = false. - For each
jfrom 1 tom, setcur[j]tocur[j-1], or toprev[j-1]whens[i-1]equalst[j-1]. - Replace
prevwithcur. - Return
prev[m].
def isSubsequence(s, t):
n, m = len(s), len(t)
# prev[j]: the first i-1 letters of s fit inside t[:j]. An empty s fits anywhere.
prev = [True] * (m + 1)
for i in range(1, n + 1):
cur = [False] * (m + 1)
for j in range(1, m + 1):
cur[j] = cur[j - 1] or (s[i - 1] == t[j - 1] and prev[j - 1])
prev = cur
return prev[m]Two pointers with greedy matching
Intuition
Keep a pointer i to the next letter of s you need, and take the first copy of it you meet in t. That choice is safe. If a valid placement uses a later copy of s[i], swapping in the earliest copy keeps the order and leaves more of t for the rest of s. So if any placement exists, the greedy one finds it.
The loop reads each letter of t at most once and keeps two indexes: O(n + m) time and O(1) memory.
Algorithm
- Set
i = 0forsandj = 0fort. - While both indexes are inside their strings, compare
s[i]witht[j]. - If they are equal, increase
i. - Increase
jin every case. - Return whether
iequals the length ofs.
def isSubsequence(s, t):
i = j = 0
while i < len(s) and j < len(t):
if s[i] == t[j]:
i += 1
j += 1
return i == len(s)
Pitfalls and edge cases
- Searching each letter of
sanywhere intinstead of after the previous match. That acceptsaecinabcde. - Using one copy of a letter twice.
noonis not a subsequence ofmoon: its singlen, at index 3, cannot be both the first and the last letter. - Returning whether
jreached the end oft. The loop often ends there whether or notswas found; onlyitells you. - Not stopping when
truns out.abcagainstabmust returnfalse. - Reading
s[i]afterireached the end ofs. In Python or Java that read throws, so checkibefore you compare.
Frequently asked questions3
What is the time complexity of Is Subsequence?
The two pointer solution runs in O(n + m) time, where n and m are the lengths of s and t, with O(1) extra memory. The loop stops after at most m steps. The prefix table takes O(n × m) time.
How is Is Subsequence related to the longest common subsequence?
s is a subsequence of t exactly when their longest common subsequence has length n, all of s. The prefix table of the first approach is the one you build for that problem. Here you only need to know whether all of s fits, so the greedy pass is enough.
What is the difference between a subsequence and a substring?
A substring is a block of consecutive letters, while a subsequence may skip letters as long as the order stays the same. ace is a subsequence of abcde but not a substring of it. Every substring is a subsequence, but not the other way around.
Similar problems
Problems that use the same ideas. Solving two or three of them is what makes a pattern stick.
def isSubsequence(s, t):
# Write code hereTest #1
Test #2
Test #3
Arguments
s = "ace" t = "abcde"
Expected Output
true