Top View of Binary Tree
מקבלים עץ בינארי המאוחסן במערך root לפי סדר ערימה: השורש באינדקס 0, הילדים של אינדקס i באינדקסים 2*i+1 (משמאל) ו־2*i+2 (מימין), ו־-1 מציין מקום ריק.
השורש נמצא בעמודה 0; ילד שמאלי נמצא עמודה אחת משמאל להורה שלו, וילד ימני נמצא עמודה אחת מימין. במבט מלמעלה רואים את הצומת הרדוד ביותר בכל עמודה; במקרה של תיקו, רואים את הצומת שמופיע ראשון ברמתו, משמאל לימין (האינדקס הקטן יותר). החזירו את הערכים שרואים, מהעמודה השמאלית ביותר לימנית ביותר.
פונקציה
- rootinteger-array
- העץ בסדר ערימה, עם -1 במקום ריק
- מחזירהinteger-array
- הערך שנראה מלמעלה בכל עמודה, מהעמודה השמאלית ביותר תחילה
אילוצים
1 ≤ root.length ≤ 32767- כל
root[i]הוא-1או ערך שעבורו0 ≤ root[i] ≤ 104, ואף ערך אינו מופיע פעמיים. root[0]לעולם אינו-1, ולכן יש בעץ לפחות צומת אחד.- המערך עשוי להסתיים בערכי
-1נוספים אחרי הצומת האחרון. - גם שני הילדים של מקום ריק ריקים, ובעץ יש לכל היותר
14רמות.
דוגמאות
- קלט
- root = [5, 2, 9, 4, 7, 1, 6]
- פלט
- [4, 2, 5, 9, 6]
- הסבר
- העמודות מכילות, משמאל לימין, את
4(עמודה -2), את2(-1), את השורש5(0), את9(1) ואת6(2). גם7ו־1נמצאים בעמודה 0, אבל השורש נמצא מעליהם.
- קלט
- root = [1, 2, -1, -1, 3, -1, -1, -1, -1, -1, 4]
- פלט
- [2, 1, 4]
- הסבר
2נמצא בעמודה -1. הילד הימני שלו,3, באינדקס 4, נמצא שוב בעמודה 0, מתחת לשורש, ולכן הוא מוסתר.4באינדקס 10, הילד הימני של3, נמצא לבדו בעמודה 1.
- קלט
- root = [6, -1, -1]
- פלט
- [6]
- הסבר
- באינדקסים 1 ו-2 מופיע
-1, ולכן6עומד לבדו בעמודה 0 ומהווה את כל התצוגה.
שאלת המשך
כשמשתמשים באובייקטים של צמתים במקום במערך, אין אינדקס שמכריע בשוויון. איך תנסח את כלל השוויון, והאם הקוד שלך לסריקה לרוחב משתנה?
רמזים
פתחו אותם אחד אחד. כל רמז חושף קצת יותר.
מספרו את העמודות: השורש הוא 0, צעד שמאלה מחסיר 1 וצעד ימינה מוסיף 1. איזה צומת בעמודה אפשר לראות מלמעלה, ואיך מבדילים בינו לבין שאר הצמתים באותה עמודה?
אם תבקר בצמתים רמה אחר רמה, ומשמאל לימין בתוך כל רמה, באיזה צומת של עמודה תיתקל ראשון?
בצע חיפוש לרוחב על זוגות (index, column), והוסף את הילד השמאלי לפני הימני. שמור ערך רק בפעם הראשונה שעמודה מופיעה, עקוב אחר העמודה הקטנה ביותר והגדולה ביותר, וקרא את העמודות לפי הסדר בסוף.
פתרון
כל עמודה מציגה את הצומת הרדוד ביותר שלה, ובמקרה של שוויון — את הצומת שמופיע ראשון בסדר לפי רמות. זה בדיוק הסדר שבו חיפוש לרוחב מבקר בצמתים, ולכן הצומת הראשון שהחיפוש פוגש בעמודה הוא התשובה. גם מעבר בחיפוש לעומק עובד, אבל הוא צריך לעקוב אחר העומקים ולשבור שוויונות בזהירות.
בחיפוש לעומק תחילה, הצומת הרדוד ביותר בכל עמודה
האינטואיציה
עבור על העץ בחיפוש לעומק, יחד עם העמודה והעומק של כל צומת, ושמור לכל עמודה את העומק ואת הערך של הצומת הרדוד ביותר שנמצא עד כה.
החלף רק כאשר העומק קטן יותר ממש. מבין שני צמתים בעלי אותו עומק, זה שמופיע ראשון בסדר השכבות יגיע ראשון בסריקה שבודקת כל תת-עץ שמאלי לפני תת-העץ הימני התואם, כי במקום שבו שני המסלולים נפרדים היא פונה שמאלה. החלפה בעומק זהה תגרום לצומת המאוחר יותר לנצח: כאשר האינדקס 3 ריק ויש צמתים באינדקסים 19 ו-23, שניהם נמצאים בעמודה -2 בעומק 4, והתשובה היא הצומת באינדקס 19.
העמודות המאוכלסות יוצרות טווח רציף אחד, מכיוון שלצומת בעמודה -3 יש אבות בעמודות -1 ו--2, לכן עקוב אחר העמודה הקטנה ביותר והגדולה ביותר. מבקרים בכל צומת פעם אחת, זמן O(n), והעומק של הרקורסיה הוא בערך h קריאות.
אלגוריתם
- בקרו בשורש עם עמודה 0 ועומק 0. חזרו מיד אם האינדקס מחוץ למערך או מכיל
-1. - אם עדיין אין צומת בעמודה, או שהעומק הזה קטן ממש מהעומק השמור, שמרו את העומק ואת הערך.
- בקרו ב־
2i+1עם עמודהcol-1, ואז ב־2i+2עם עמודהcol+1, ברמה אחת עמוק יותר. - החזירו את הערכים השמורים מהעמודה הקטנה ביותר לגדולה ביותר.
def topView(root):
n = len(root)
best = {} # column -> (depth, value) of the shallowest node found so far
def visit(i, col, depth):
if i >= n or root[i] == -1:
return
# Replace only on a strictly smaller depth: on a tie, the node
# met first is the one further left, which comes first in level order.
if col not in best or depth < best[col][0]:
best[col] = (depth, root[i])
visit(2 * i + 1, col - 1, depth + 1)
visit(2 * i + 2, col + 1, depth + 1)
visit(0, 0, 0)
# The columns that hold a node form one unbroken range.
return [best[col][1] for col in range(min(best), max(best) + 1)]חיפוש לרוחב, הצומת הראשון בכל עמודה
האינטואיציה
תור שמקבל את השורש, ואז את הילד השמאלי של כל צומת ולאחר מכן את הילד הימני שלו, מוציא את הצמתים לפי סדר השכבות: כל צומת בעומק d לפני כל צומת בעומק d+1, וכל שכבה משמאל לימין. זו העדיפות של הבעיה, ולכן הצומת הראשון שעמודה מקבלת הוא זה שנראה מלמעלה, וכל צומת שמגיע אחריו נדלג.
כל צומת דורש חיפוש אחד במפה, ובסך הכול O(n). התור יכול להכיל שכבה שלמה, עד כמחצית מהצמתים, ולכן סיבוכיות המקום היא O(n).
אלגוריתם
- הכנס את הזוג (אינדקס 0, עמודה 0) לתור.
- הוצא את הזוג (
i,col). אם אין במפה ערך עבורcol, שמור שם אתroot[i]. - הוסף (
2i+1,col-1), ואז (2i+2,col+1), עבור כל ילד שקיים. - כשהתור ריק, החזר את ערכי המפה מהעמודה הקטנה ביותר ועד לגדולה ביותר.
from collections import deque
def topView(root):
n = len(root)
top = {} # column -> value of the first node seen in that column
queue = deque([(0, 0)]) # (node index, column); the root is never empty
while queue:
i, col = queue.popleft()
# The queue hands out shallower nodes first, and left before right
# on one level, so the first node of a column is the one seen from above.
if col not in top:
top[col] = root[i]
left, right = 2 * i + 1, 2 * i + 2
if left < n and root[left] != -1:
queue.append((left, col - 1))
if right < n and root[right] != -1:
queue.append((right, col + 1))
# The columns that hold a node form one unbroken range.
return [top[col] for col in range(min(top), max(top) + 1)]
מלכודות ומקרי קצה
- שמירת הצומת הראשון שבו נתקל סריקה לעומק. אם לילד השמאלי של השורש יש ילד ימני שלו יש ילד ימני (עמודה 1, עומק 3), הסריקה תגיע אליו לפני שתגיע לילד הימני של השורש (עמודה 1, עומק 1).
- הכנסת הילד הימני לתור ראשון: בשוויון, הצומת הלא נכון קודם.
- מיון העמודות כטקסט:
"-1"ממוין לפני"-2". - הנחה שתת-העץ השמאלי ממלא את העמודות השליליות. בדוגמה 2 הצומת
4נמצא בתת-העץ השמאלי של השורש ובעמודה 1.
שאלות נפוצות3
מהי המבט העליון של עץ בינארי?
מספרו את העמודות החל מהשורש ב־0, עמודה אחת שמאלה עבור צומת בן שמאלי ועמודה אחת ימינה עבור צומת בן ימני. המבט העליון מציג, עבור כל עמודה משמאל לימין, את הצומת הקרוב ביותר לשורש.
מהי סיבוכיות הזמן של המבט העליון של עץ בינארי?
חיפוש לרוחב מבקר בכל אחד מ־n הצמתים פעם אחת, עם חיפוש במפה בזמן O(1) לכל צומת, ולכן זמן הריצה שלו הוא O(n) אם עוקבים אחר העמודה הקטנה ביותר והגדולה ביותר. מפה ממוינת של העמודות הופכת את זמן הריצה ל־O(n log w) עבור w עמודות.
במה המבט העליון שונה מהמבט השמאלי ומהמבט התחתון?
התצוגה השמאלית בוחרת את הצומת הראשון בכל רמה. התצוגה העליונה בוחרת את הצומת הרדוד ביותר בכל עמודה. התצוגה התחתונה בוחרת את הצומת העמוק ביותר בכל עמודה, ולכן חיפוש לרוחב עבור תצוגה זו דורס את הערך בכל עמודה במקום לשמור את הצומת הראשון.
בעיות דומות
בעיות שמשתמשות באותם רעיונות. פתרון של שתיים או שלוש מהן מקבע את הדפוס.
def topView(root):
# כתבו כאן את הקודבדיקה #1
בדיקה #2
בדיקה #3
ארגומנט
root = [5, 2, 9, 4, 7, 1, 6]
הפלט הצפוי
[4, 2, 5, 9, 6]