Is Subsequence
נתונות לך שתי מחרוזות, s ו-t. החזר true אם אפשר להפוך את t ל-s על ידי מחיקת חלק מהאותיות שלה (ייתכן שאף אחת), תוך שמירה על הסדר של האותיות שנותרו, ו-false אחרת. לדוגמה, ace היא תת-רצף של abcde, אבל aec אינה.
פונקציה
- sstring
- המחרוזת לחיפוש
- tstring
- המחרוזת שממנה יש למחוק אותיות
- מחזירהboolean
- נכון אם ניתן לקרוא את s בתוך t לפי הסדר, אולי עם פערים
אילוצים
1 ≤ s.length ≤ 3 × 1041 ≤ t.length ≤ 5 × 104sandtמכילות רק אותיות אנגליות קטנות.
דוגמאות
- קלט
- s = "ace"t = "abcde"
- פלט
- true
- הסבר
- מחקו את
bואתdמתוךabcde, ויישארace, באותו סדר.
- קלט
- s = "aec"t = "abcde"
- פלט
- false
- הסבר
- ל־
tיש את כל שלוש האותיות, אבל ה־cהיחידה נמצאת לפני ה־eהיחידה. אחרי שתשתמשו ב־eשבאינדקס 4, לא יישאר מימינהc.
- קלט
- s = "moon"t = "monsoon"
- פלט
- true
- הסבר
- השתמשו ב־
mבאינדקס 0, ב־oבאינדקסים 1 ו־4, וב־nבאינדקס 6 שלmonsoon. האותיות שביניהם נמחקות.
שאלת המשך
אם t נשאר קבוע ואתה בודק מולו מיליון מחרוזות s, איך היית מכין את t כך שכל בדיקה תהיה מהירה יותר מקריאה של כל t?
רמזים
פתחו אותם אחד אחד. כל רמז חושף קצת יותר.
הסתכלו על האות הראשונה של
s. באיזה מופע שלה ב־tכדאי להשתמש?השתמשו בהעתק המוקדם ביותר. בחירה בהעתק מאוחר יותר יכולה רק להשאיר פחות מ־
tעבור שאריתs, לכן הבחירה המוקדמת ביותר לעולם אינה גרועה יותר.שמור אינדקס אחד לתוך
sואחד לתוךt. עבור דרךtאות אחת בכל פעם, קדם את האינדקס לתוךsבכל התאמה, ובדוק בסוף אם הוא הגיע לסוף שלs.
פתרון
תת־רצף יכול לדלג על אותיות של t בכל מקום, מה שמרמז שכדאי לנסות מיקומים רבים של s. אין צורך בכך: התאמת כל אות של s במקום המוקדם ביותר שבו היא יכולה להופיע לעולם אינה גרועה יותר מכל בחירה אחרת, ולכן מעבר אחד משמאל לימין עם שני מצביעים קובע זאת.
תכנות דינמי על קידומות
נכונה, אבל לא מסתיימת בבדיקות הגדולות ביותר
האינטואיציה
נסמן ב־dp[i][j] אם האותיות הראשונות i של s מתאימות לאותיות הראשונות j של t. הן מתאימות אם הן כבר מתאימות בלי t[j-1], או אם s[i-1] שווה ל־t[j-1] והאותיות הראשונות i-1 מתאימות ל־t[:j-1]. הקידומת הריקה מתאימה בכל מקום, ושורה i קוראת רק את שורה i-1, לכן מספיקות שתי שורות באורך m+1.
הטבלה ממלאת כל תא: כאשר ל־s יש 25,000 אותיות ול־t יש 50,000, מדובר ב־1.25 × 10^9 תאים.
אלגוריתם
- צרו שורה
prevשלm+1ערכים, כולםtrue: מחרוזת ריקהsמתאימה לכל קידומת שלt. - עבור כל
iמ־1 עדn, צרו שורהcurעםcur[0] = false. - עבור כל
jמ־1 עדm, הגדירו אתcur[j]כ־cur[j-1], או כ־prev[j-1]כאשרs[i-1]שווה ל־t[j-1]. - החליפו את
prevב־cur. - החזירו את
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]שני מצביעים עם התאמה חמדנית
האינטואיציה
החזק מצביע i לאות הבאה ב־s שאתה צריך, וקח את העותק הראשון שלה שאתה פוגש ב־t. הבחירה הזאת בטוחה. אם שיבוץ תקין משתמש בעותק מאוחר יותר של s[i], החלפתו בעותק המוקדם ביותר שומרת על הסדר ומשאירה יותר מ־t עבור שאר האותיות של s. לכן, אם קיים שיבוץ כלשהו, השיטה החמדנית תמצא אותו.
הלולאה קוראת כל אות ב־t לכל היותר פעם אחת ושומרת שני אינדקסים: זמן O(n + m) וזיכרון O(1).
אלגוריתם
- הגדר את
i = 0עבורsואתj = 0עבורt. - כל עוד שני האינדקסים נמצאים בתוך המחרוזות שלהם, השווה את
s[i]עםt[j]. - אם הם שווים, הגדל את
i. - הגדל את
jבכל מקרה. - החזר האם
iשווה לאורך שלs.
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)
מלכודות ומקרי קצה
- חיפוש כל אות של
sבכל מקום ב-tבמקום אחרי ההתאמה הקודמת. כךaecיתקבל כתת-רצף שלabcde. - שימוש באותו עותק של אות פעמיים.
noonאינו תת-רצף שלmoon: ה-nהיחידה, באינדקס 3, לא יכולה להיות גם האות הראשונה וגם האות האחרונה. - החזרה של השאלה האם
jהגיע לסוף שלt. הלולאה מסתיימת שם לעיתים קרובות, בין אםsנמצא ובין אם לא; רקiאומר לך. - אי-עצירה כשנגמר
t. עבורabcמולabיש להחזירfalse. - קריאה של
s[i]אחרי ש-iהגיע לסוף שלs. ב-Python או ב-Java הקריאה הזאת גורמת לחריגה, לכן יש לבדוק אתiלפני שמשווים.
שאלות נפוצות3
מהי סיבוכיות הזמן של Is Subsequence?
פתרון שני המצביעים פועל בזמן O(n + m), כאשר n ו-m הם האורכים של s ו-t, תוך שימוש בזיכרון נוסף של O(1). הלולאה נעצרת לאחר לכל היותר m צעדים. טבלת הקידומות דורשת זמן O(n × m).
מה הקשר בין תת־רצף לרצף המשותף הארוך ביותר?
s היא תת-רצף של t בדיוק כאשר אורך תת-הרצף המשותף הארוך ביותר שלהם הוא n, כלומר אורך s. טבלת הקידומות של הגישה הראשונה היא זו שבונים עבור הבעיה הזאת. כאן צריך לדעת רק אם כל s מתאים, ולכן מעבר חמדני מספיק.
מה ההבדל בין תת־רצף לתת־מחרוזת?
תת־מחרוזת היא רצף של אותיות סמוכות, ואילו תת־רצף יכול לדלג על אותיות כל עוד הסדר נשאר זהה. ace הוא תת־רצף של abcde, אבל אינו תת־מחרוזת שלו. כל תת־מחרוזת היא תת־רצף, אך לא להפך.
בעיות דומות
בעיות שמשתמשות באותם רעיונות. פתרון של שתיים או שלוש מהן מקבע את הדפוס.
def isSubsequence(s, t):
# כתבו כאן את הקודבדיקה #1
בדיקה #2
בדיקה #3
ארגומנטים
s = "ace" t = "abcde"
הפלט הצפוי
true