NeetCode #5LC-392EasyArrays & Hashing
← Back to All Problems#5 · #392 · Is Subsequence(判断子序列)
📌 Problem Statement & Constraints
Given two strings
s and t, return true if s is a subsequence of t. A subsequence is formed by deleting some characters without changing the relative order of the rest. Constraints: 0 <= s.length <= 100, 0 <= t.length <= 10^4.💡 Core Algorithmic Approaches
- A two-pointer scan is the natural solution: one pointer walks
s, the other walkst. - Advance the
tpointer on every step. Advance thespointer only when the characters match. - If the
spointer reaches the end ofs, every character was matched in order, sosis a subsequence. - The
spointer is the loop condition, so the scan stops as soon assis fully consumed -- an early exit that helps on long inputs.
💻 Benchmark Python3 Implementation
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
i = 0 # pointer into s
for ch in t:
if i < len(s) and ch == s[i]:
i += 1 # matched one character of s
if i == len(s):
return True # early exit
return i == len(s)
# Follow-up for many queries: precompute next-occurrence tables
class Solution2:
def isSubsequence(self, s: str, t: str) -> bool:
from bisect import bisect_left
from collections import defaultdict
pos = defaultdict(list)
for j, ch in enumerate(t):
pos[ch].append(j)
cur = 0
for ch in s:
lst = pos.get(ch)
if not lst:
return False
k = bisect_left(lst, cur) # earliest occurrence at or after cur
if k == len(lst):
return False
cur = lst[k] + 1
return True⚡ Complexity Deep Dive
⏱️ Time Complexity
O(|t|) for the two-pointer version: t is scanned once. The precomputation variant is O(|t|) build plus O(|s| log |t|) per query, which pays off when the same t is queried many times.
💾 Space Complexity
O(1) for the two-pointer version; O(|t|) for the position index in the variant.
⚠️ Interview Pitfalls & Follow-ups
- Confusing subsequence with substring: a subsequence need not be contiguous, so
"ace"is a subsequence of"abcde". - Scanning
sin the outer loop: thentwould be rescanned for each character ofs, giving O(|s| * |t|). - Forgetting the empty-
scase: an empty string is a subsequence of any string, and the loop returnsi == len(s) == 0, which isTrue. - Using
t.find(ch, i)in a loop: correct, butfindrestarts its scan each time; the linear two-pointer walk is cleaner and strictly O(|t|).