Bottom View of Binary Tree
המערך root מכיל עץ בינארי: השורש באינדקס 0, הילדים של אינדקס i באינדקסים 2i+1 (שמאל) ו־2i+2 (ימין), -1 עבור מקום ריק, וייתכנו ערכי -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, 1, 9, 6]
- הסבר
- עמודה 0 מכילה את
5ומתחתיה, שתי רמות למטה, את7(אינדקס 4) ואת1(אינדקס 5).1מופיע מאוחר יותר באותה רמה, ולכן הוא מוצג. העמודות -2, -1, 1 ו-2 מכילות את4,2,9ו-6.
- קלט
- root = [1, 2, 3, -1, 4, -1, -1, -1, -1, 5]
- פלט
- [5, 4, 3]
- הסבר
- עמודה -1 מכילה את
2ומתחתיה, שתי רמות למטה, את5(אינדקס 9), ולכן רואים את5. בעמודה 0,4נמצא מתחת לשורש. עמודה 1 מכילה רק את3.
- קלט
- root = [8, -1, -1, -1]
- פלט
- [8]
- הסבר
- כל המיקומים אחרי השורש הם
-1, ולכן8הוא הצומת היחיד ועמודה 0 היא העמודה היחידה.
שאלת המשך
אם במקרה של תיקו ברמה העמוקה ביותר של עמודה צריך להחזיר את כל הצמתים שבמקום הראשון, משמאל לימין, איך ישתנה המימוש בחיפוש לרוחב? מה יתקבל בעמודה 0 של דוגמה 1?
רמזים
פתחו אותם אחד אחד. כל רמז חושף קצת יותר.
מספרים את העמודות: השורש הוא 0, צעד שמאלה מחסיר 1 וצעד ימינה מוסיף 1. כשמסתכלים מלמטה, איזה צומת בעמודה רואים, ומה קורה אם שני צמתים באותה עמודה נמצאים באותו גובה?
עוברים על הצמתים רמה אחר רמה, משמאל לימין בתוך כל רמה, וכותבים את הערך של כל צומת בעמודה שלו, תוך החלפת הערך שהיה בה. מה נשאר בכל עמודה בסוף?
השתמשו בתור של זוגות (אינדקס, עמודה), והכניסו את הילד השמאלי לתור לפני הילד הימני. דרסו את הערך של העמודה עבור כל צומת, עקבו אחר העמודה הקטנה ביותר והגדולה ביותר, ובסוף קראו את העמודות לפי הסדר.
פתרון
כל עמודה מציגה את הצומת העמוק ביותר שלה, ובמקרה של שוויון נבחר הצומת שמופיע מאוחר יותר בסדר הרמות. חיפוש לרוחב מבקר בצמתים לפי סדר הרמות, ולכן הצומת האחרון שבו הוא מבקר בעמודה הוא התשובה: תן לכל צומת לדרוס את המשבצת של העמודה שלו.
חיפוש לעומק תחילה, הצומת העמוק ביותר בכל עמודה
האינטואיציה
עבור על העץ לעומק, תוך העברת העמודה והעומק של כל צומת, ושמור עבור כל עמודה את העומק ואת הערך של הצומת העמוק ביותר שנמצא בה עד כה.
ביקור בתת-העץ השמאלי לפני הימני פוגש את הצמתים של כל רמה משמאל לימין, ולכן הצומת הרצוי הוא האחרון שפוגשים: החלף כשהעומק גדול או שווה. בדוגמה 1 הסריקה פוגשת את 7 ואז את 1, שניהם בעומק 2 בעמודה 0, ו-1 תופס את מקומה של העמודה.
העמודות יוצרות טווח רציף אחד, לכן עקוב אחר הקטנה והגדולה ביותר וקרא כל עמודה שביניהן. ביקור אחד בכל צומת מעניק זמן ריצה של O(n), והקריאות הרקורסיביות מגיעות לעומק של h קריאות עבור עץ בעל h רמות.
אלגוריתם
- בקרו בשורש עם עמודה 0 ועומק 0.
- דלגו על האינדקס
iכאשר הוא מעבר לסוף המערך או כאשרroot[i]הוא-1. - אם עדיין אין צומת בעמודה, או שעומק זה גדול או שווה לעומק השמור, שמרו את העומק ואת הערך.
- בקרו ב-
2i+1עם העמודהcol-1, ואז ב-2i+2עם העמודהcol+1, שניהם ברמה אחת עמוקה יותר. - החזירו את הערכים השמורים, מהעמודה הקטנה ביותר תחילה.
def bottomView(root):
n = len(root)
best = {} # column -> (depth, value) of the deepest node found so far
def visit(i, col, depth):
if i >= n or root[i] == -1:
return
# Replace on a greater or equal depth: on a tie, the node
# met last is the one further right, which comes later 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, וכל רמה משמאל לימין. מבין הצמתים בעמודה, העמוק ביותר יוצא אחרון, ובמקרה של שוויון — זה שנמצא ימינה יותר. לכן, כשכל צומת מחליף את הערך במשבצת של העמודה שלו, כל משבצת מכילה בסופו של דבר את הצומת שנראה מלמטה, ואין צורך להשוות עומקים.
בדוגמה 1, 7 כותב לעמודה 0 ו-1 מחליף אותו.
כל צומת דורש ביקור אחד בתור וכתיבה אחת למפה: זמן O(n). התור יכול להכיל רמה שלמה, בערך מחצית מהצמתים, ולכן המקום הוא O(n).
אלגוריתם
- הכניסו לתור את הזוג (אינדקס 0, עמודה 0).
- הוציאו זוג (
i,col) ושמרו אתroot[i]תחתcol, תוך החלפת כל ערך קודם. - הוסיפו (
2i+1,col-1), ואז (2i+2,col+1), עבור כל ילד שנמצא בתוך המערך ואינו-1. - חזרו על הפעולה עד שהתור מתרוקן, תוך מעקב אחר העמודה הקטנה ביותר והגדולה ביותר.
- החזירו את ערכי המפה, החל מהעמודה הקטנה ביותר.
from collections import deque
def bottomView(root):
n = len(root)
bottom = {} # column -> value of the last 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 deeper nodes later, and right after left
# on one level, so the last node to reach a column is the one seen from below.
bottom[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 [bottom[col] for col in range(min(bottom), max(bottom) + 1)]
מלכודות ומקרי קצה
- החלפה רק כאשר העומק גדול יותר באופן מחמיר בגרסת החיפוש לעומק. לדוגמה, במצב 1 נשאר
7במקום1. - שמירת הצומת הראשון שכל חיפוש לרוחב מגיע אליו בכל עמודה. זוהי התצוגה מלמעלה.
- הוספת הילד הימני לתור לפני הילד השמאלי. כך כל רמה מתקבלת מימין לשמאל, ובמקרה של שוויון נבחר הצומת הלא נכון.
- בחירת העלים כתשובה. במצב 1 העלה
7מוסתר על ידי1, ובמצב 2 לצומת4יש ילד, ובכל זאת הוא נמצא בתחתית עמודה 0. - מיון העמודות כטקסט. כמחרוזות,
"-10"מופיע בין"-1"ל-"-2".
שאלות נפוצות3
מהי התצוגה התחתונה של עץ בינארי?
תן לעמודת השורש את הערך 0, לכל צעד שמאלה עמודה אחת פחות ולכל צעד ימינה עמודה אחת יותר. התצוגה התחתונה מציגה את הצומת העמוק ביותר בכל עמודה, משמאל לימין. הבעיה הזו שוברת שוויון בעומק לפי סדר רמות, ומשאירה את הצומת המאוחר יותר.
מהי סיבוכיות הזמן של תצוגת התחתית של עץ בינארי?
O(n) עבור n צמתים: החיפוש לרוחב מטפל בכל צומת פעם אחת עם כתיבה אחת למפת גיבוב. מפה ממוינת כמו מפת עץ מוסיפה גורם של log w עבור w עמודות.
מה ההבדל בין המבט מלמעלה למבט מלמטה?
בשניהם בוחרים צומת אחד בכל עמודה. המבט העליון בוחר את הצומת הרדוד ביותר, והמבט התחתון את העמוק ביותר. בגרסת החיפוש לרוחב, המבט העליון שומר את הערך הראשון שמתקבל בעמודה; המבט התחתון מחליף אותו בכל פעם.
בעיות דומות
בעיות שמשתמשות באותם רעיונות. פתרון של שתיים או שלוש מהן מקבע את הדפוס.
def bottomView(root):
# יש לכתוב כאן קודבדיקה #1
בדיקה #2
בדיקה #3
ארגומנט
root = [5, 2, 9, 4, 7, 1, 6]
הפלט הצפוי
[4, 2, 1, 9, 6]