Binary Tree Zigzag Level Order Traversal
נתון לך עץ בינארי המאוחסן במערך root. השורש נמצא באינדקס 0, הילדים של אינדקס i נמצאים באינדקסים 2*i+1 (שמאל) ו-2*i+2 (ימין), -1 מסמן מקום ריק, והמערך עשוי להסתיים בערכי -1 נוספים.
החזר את ערכי הצמתים לפי רמות בסדר זיגזג: רמת השורש משמאל לימין, הרמה הבאה מימין לשמאל, וכך הלאה, תוך החלפה לסירוגין עד לרמה העמוקה ביותר. הדבר נקרא גם סריקת סדר רמות בספירלה.
פונקציה
- rootinteger-array
- העץ בסדר ערימה, כאשר -1 מציין מקום ריק
- מחזירהinteger-2d-array
- רשימה אחת של ערכים לכל רמה, כשהרמה העליונה ראשונה, לסירוגין משמאל לימין ומימין לשמאל
אילוצים
1 ≤ root.length ≤ 32767- כל
root[i]הוא-1או ערך המקיים0 ≤ root[i] ≤ 104. - ערכי הצמתים שונים זה מזה.
root[0]אף פעם לא שווה ל־-1, ולכן יש לעץ לפחות צומת אחד.- המערך עשוי להסתיים בערכי
-1נוספים אחרי הצומת האחרון. - גם שני הילדים של מקום ריק ריקים, ולעץ יש לכל היותר
14רמות.
דוגמאות
- קלט
- root = [8, 12, 5, 1, 9, -1, 14, -1, -1, 4, 2, -1, -1, 11]
- פלט
- [[8], [5, 12], [1, 9, 14], [11, 2, 4]]
- הסבר
- אם קוראים משמאל לימין, הרמות הן
8;12, 5;1, 9, 14;4, 2, 11. הרמות 1 ו-3 נקראות בכיוון ההפוך:[5, 12]ו-[11, 2, 4].
- קלט
- root = [1, 2, 3, 4, 5, 6, 7, -1, -1]
- פלט
- [[1], [3, 2], [4, 5, 6, 7]]
- הסבר
- עץ מלא בעל שלוש רמות: הרמה האמצעית מתהפכת ל־
[3, 2]והרמה השלישית חוזרת לרוץ משמאל לימין. שתי הרשומות האחרונות של-1הן המקומות הריקים של הילדים של4.
- קלט
- root = [5]
- פלט
- [[5]]
- הסבר
- צומת יחיד הוא רמה אחת של ערך אחד, שנקרא בשני הכיוונים.
שאלת המשך
האם תוכל להציב כל ערך ישירות במקומו הסופי בשורה שלו, בלי להפוך ובלי להשתמש במחסנית שנייה?
רמזים
פתחו אותם אחד אחד. כל רמז חושף קצת יותר.
אספו תחילה את הרמות, כל אחת משמאל לימין, כמו בסריקה רגילה לפי סדר הרמות. אילו מהרשימות האלה צריכות להשתנות, ואיך?
מספרים את הרמות החל מ־0 בשורש. קוראים את הרמות האי־זוגיות לאחור. היפוך של רשימה עולה כמו קריאתה, ולכן היפוך הרמות האלה שומר על זמן ריצה ליניארי לכל התהליך.
כדי לדלג על ההיפוך, שמרו את הרמה הנוכחית במחסנית והכניסו את הילדים למחסנית שנייה תוך כדי שליפה: קודם את הילד השמאלי כשקוראים משמאל לימין, וקודם את הילד הימני כשקוראים מימין לשמאל. לאחר מכן, שליפת האיברים מהמחסנית השנייה תציג את הרמה הבאה בסדר הדרוש.
פתרון
הרמות מתקבלות מחיפוש רגיל לרוחב. החלק החדש הוא הכיוון: כל רמה שנייה מתקבלת בסדר הפוך. אפשר להפוך את השורות האי-זוגיות אחרי שבונים אותן, או להחזיק כל רמה במחסנית, שמחזירה פריטים בסדר הפוך לסדר שבו נדחפו.
סדר לפי רמות, ואז הפוך כל רמה שנייה
האינטואיציה
תור המעובד ברמה אחת בכל סבב מניב כל רמה משמאל לימין. מספור הרמות מתחיל ב־0 בשורש: בסריקת הזיגזג הרמות האי־זוגיות נקראות מימין לשמאל, ולכן הפיכת השורות האלה בלבד נותנת את התשובה.
החיפוש מבקר בכל צומת פעם אחת, וכל היפוך נוגע בכל ערך פעם נוספת, לכן זמן הריצה הוא O(n).
אלגוריתם
- התחילו תור עם אינדקס
0. - בכל סבב, שמרו את אורך התור
s, הוציאו מהתורsאינדקסים והכניסו אותם לשורה חדשה, והוסיפו לתור את הילדים הקיימים של כל אחד מהם, תחילה את השמאלי ואז את הימני. - אם בתשובה כבר יש מספר אי־זוגי של שורות, הפכו את סדר השורה.
- הוסיפו את השורה והמשיכו עד שהתור יתרוקן.
from collections import deque
def zigzagLevelOrder(root):
n = len(root)
levels = []
queue = deque([0]) # node indexes; the root is never empty
while queue:
row = []
for _ in range(len(queue)): # exactly the nodes of the current level
i = queue.popleft()
row.append(root[i])
for child in (2 * i + 1, 2 * i + 2):
if child < n and root[child] != -1:
queue.append(child)
if len(levels) % 2 == 1: # levels 1, 3, 5, ... read from right to left
row.reverse()
levels.append(row)
return levelsשתי מחסניות
האינטואיציה
שמרו את הרמה הנוכחית במחסנית שמבצעת שליפה בכיוון שבו קוראים את הרמה, ודחפו את הילדים של כל צומת שנשלף למחסנית שנייה. כשהמחסנית הנוכחית מתרוקנת, המחסנית השנייה מכילה את הרמה הבאה.
סדר הדחיפה הוא מה שגורם לזה לעבוד. בשליפה משמאל לימין, דחפו את הילד השמאלי של כל צומת ואז את הילד הימני שלו: הילד הימני של הצומת הימני ביותר נדחף אחרון, ולכן מהרמה הבאה מתבצעת שליפה מהקצה הימני שלה. בשליפה מימין לשמאל, דחפו קודם את הילד הימני ואז את השמאלי, והילד השמאלי של הצומת השמאלי ביותר נדחף אחרון.
דוגמה 1: שלפו את 8, ודחפו את 12, 5. שלפו את 5, 12, ואז דחפו את 14 מהצומת 5 ואת 9, 1 מהצומת 12. שלפו את 1, 9, 14, ואז דחפו את 4, 2 מהצומת 9 ואת 11 מהצומת 14. שלפו את 11, 2, 4.
כל צומת נדחף ונשלף פעם אחת: זמן ריצה של O(n), ללא שלב היפוך.
אלגוריתם
- דחוף את האינדקס
0של השורש למחסנית והתחל לקרוא משמאל לימין. - כל עוד המחסנית אינה ריקה, שלוף כל אינדקס והוסף אותו לשורה חדשה, ודחוף את הילדים הקיימים של כל צומת שנשלף למחסנית שנייה: קודם שמאל ואז ימין כשקוראים משמאל לימין, וקודם ימין ואז שמאל אחרת.
- הוסף את השורה לתשובה.
- הפוך את המחסנית השנייה למחסנית הנוכחית, הפוך את הכיוון וחזור על הפעולה.
def zigzagLevelOrder(root):
n = len(root)
levels = []
current = [0] # a stack of node indexes; the root is never empty
left_to_right = True
while current:
row = []
upcoming = [] # the next level, pushed while this one pops
while current:
i = current.pop()
row.append(root[i])
# Push so that the next level pops out in its own direction.
if left_to_right:
children = (2 * i + 1, 2 * i + 2)
else:
children = (2 * i + 2, 2 * i + 1)
for child in children:
if child < n and root[child] != -1:
upcoming.append(child)
levels.append(row)
current = upcoming
left_to_right = not left_to_right
return levels
מלכודות ומקרי קצה
- היפוך הסדר שבו הילדים נכנסים לתור יחיד. האחים מחליפים סדר, אבל ההורים שומרים על הסדר שלהם: בדוגמה 1, הרמה השלישית מתקבלת כך:
[9, 1, 14]. - ספירת הרמות החל מ־1. כל היפוך מזיז את התוצאה רמה אחת, ובדוגמה 1 מתקבל
[[8], [12, 5], [14, 9, 1], [4, 2, 11]]. - שימוש באותו סדר דחיפה בכל הרמות בגרסה עם שתי מחסניות. לכן, הרמה השלישית בדוגמה 1 נשלפת כך:
[9, 1, 14]. - דחיפת הילדים למחסנית שעדיין שולפים ממנה. הם נשלפים לפני שאר הרמה של ההורה שלהם.
- מיון שורה במקום היפוך שלה. הרמה האחרונה בדוגמה 1 חייבת להיות
[11, 2, 4], ושום מיון לא יפיק אותה.
שאלות נפוצות3
מה ההבדל בין מעבר בסדר זיגזג למעבר בסדר רמות?
הם עוברים באותן רמות עם אותם צמתים. סריקה לפי רמות קוראת כל רמה משמאל לימין; סריקת זיגזג קוראת כל רמה שנייה מימין לשמאל, החל מהרמה שמתחת לשורש. שתיהן פועלות בזמן O(n).
האם מעבר זיגזג זהה למעבר בסדר ספירלי של עץ?
כן, שני השמות מתארים את אותו מסלול. חלק מהטקסטים מתחילים את הרמה השנייה משמאל במקום מימין, מה שרק מזיז את ההיפוכים ברמה אחת. כאן הרמה של השורש נעה משמאל לימין, והרמה הבאה מימין לשמאל.
האם אפשר לבצע מעבר ברמות בסדר זיגזג באמצעות deque?
כן. יש לשמור כל רמה ב־deque בסדר משמאל לימין. ברמה שנעים בה משמאל לימין, לוקחים צמתים מהחזית ומוסיפים את ילדיהם, קודם את השמאלי ואז את הימני, מאחור. ברמה שנעים בה מימין לשמאל, לוקחים צמתים מאחור ומוסיפים את ילדיהם, קודם את הימני ואז את השמאלי, מלפנים. המספר שלוקחים בתחילת סבב עדיין מסמן את הרמה.
בעיות דומות
בעיות שמשתמשות באותם רעיונות. פתרון של שתיים או שלוש מהן מקבע את הדפוס.
def zigzagLevelOrder(root):
# כתבו כאן את הקודבדיקה #1
בדיקה #2
בדיקה #3
ארגומנט
root = [8, 12, 5, 1, 9, -1, 14, -1, -1, 4, 2, -1, -1, 11]
הפלט הצפוי
[[8], [5, 12], [1, 9, 14], [11, 2, 4]]