Trapping Rain Water
שורה של עמודות עומדת זו לצד זו, כל אחת ברוחב של יחידה אחת: height[i] הוא הגובה של עמודה i. מי גשם מצטברים בשקעים שבין העמודות. מים נשארים מעל עמודה רק אם יש עמודה גבוהה יותר משמאל לה וגם עמודה גבוהה יותר מימין לה; מעבר לעמודה הראשונה והאחרונה הם זורמים החוצה. החזירו את המספר הכולל של יחידות המים שהשורה מכילה.
פונקציה
- heightinteger-array
- הגובה של כל עמודה, משמאל לימין
- מחזירהinteger
- כמות יחידות המים הכלואות הכוללת
אילוצים
1 ≤ height.length ≤ 2 × 1040 ≤ height[i] ≤ 105- כל עמודה היא ברוחב יחידה אחת, והמים אינם נשארים מעבר לעמודה הראשונה או האחרונה.
דוגמאות
- קלט
- height = [0, 3, 1, 0, 2, 5, 1, 2]
- פלט
- 7
- הסבר
- בין 3 ל־5 הרמה היא 3: 2 יחידות מעל 1, 3 מעל 0, 1 מעל 2. ה־1 המאוחר יותר נמצא בין 5 ל־2, ולכן הוא שווה ל־1. כך מתקבל 7.
- קלט
- height = [4, 1, 3, 0, 5]
- פלט
- 8
- הסבר
- הקיר הנמוך הוא 4, ולכן השקע מתמלא עד לגובה 4: 3 יחידות מעל ה־1, 1 מעל ה־3, 4 מעל ה־0, ובסך הכול 8. המים גולשים מעל ה־4 לפני שה־5 רלוונטי.
- קלט
- height = [1, 2, 4, 2, 1]
- פלט
- 0
- הסבר
- העמודות עולות עד 4 ושוב יורדות. לכל עמודה יש צד שאין אחריו שום דבר גבוה יותר, ולכן המים זורמים החוצה והתשובה היא 0.
שאלת המשך
נניח שהעמודים יוצרים רשת דו־ממדית של גבהים, והמים יכולים לזרום החוצה בכל ארבעת הכיוונים. איך תחשב את כמות המים שנלכדה במקרה כזה?
רמזים
פתחו אותם אחד אחד. כל רמז חושף קצת יותר.
שכח את השורה כולה והסתכל על עמודה אחת. כמה גבוה יכולים המים לעמוד מעל עמודה
i, ואילו עמודות קובעות את הגובה הזה?מפלס המים מעל העמודה
iהוא הקטן מבין שני מספרים: העמודה הגבוהה ביותר מתחילת המערך ועדi, והעמודה הגבוהה ביותר מ־iועד לסוף. העמודהiמכילה את המפלס הזה פחות הגובה שלה. אפשר לחשב את שני המקסימום המצטברים במעבר אחד מכל קצה.אתה צריך רק את הקטן מבין שני המקסימום. הצב מצביע אחד בכל קצה ושמור את הרף הגבוה ביותר שכל מצביע עבר. הרמה של המצביע שנמצא ליד הרף הנמוך יותר נקבעת לפי המקסימום המצטבר שלו: הוסף את המים האלה והזז את המצביע הזה פנימה. עצור כשהמצביעים נפגשים.
פתרון
כמות המים מעל עמוד תלויה בעמודים שיכולים להיות רחוקים ממנו משני הצדדים. הגובה שלה הוא הנמוך מבין העמוד הגבוה ביותר שמשמאלו והעמוד הגבוה ביותר שמימינו, והעמוד מחזיק כמות מים השווה לגובה הזה פחות הגובה שלו עצמו. הגישות נבדלות רק באופן שבו הן מוצאות את שני הערכים המרביים האלה.
סרוק את שני הצדדים של כל פס
נכונה, אבל לא מסתיימת בבדיקות הגדולות ביותר
האינטואיציה
עבור כל עמודה i, סרוק מ־0 עד i כדי למצוא את העמודה הגבוהה ביותר משמאל, ומ־i עד הסוף כדי למצוא את הגבוהה ביותר מימין, ואז הוסף את min(leftMax, rightMax) - height[i]. הכללת העמודה i בשתי הסריקות מונעת מהכמות להפוך לשלילית ונותנת לעמודות שבקצוות ערך 0.
כל עמודה סורקת את כל השורה: n = 2 × 10^4 פירושו 4 × 10^8 קריאות, שרובן חוזרות על הסריקה של העמודה הקודמת.
אלגוריתם
- הגדר את
waterל-0. - עבור כל אינדקס
i, סרוק מ-0 עדiכדי למצוא אתleftMax. - סרוק מ-
iעד לאינדקס האחרון כדי למצוא אתrightMax. - הוסף את
min(leftMax, rightMax) - height[i]אלwater. - החזר את
water.
def trap(height):
n = len(height)
water = 0
for i in range(n):
# The tallest bar at or left of i, and the tallest at or right of i.
left_max = 0
for j in range(i + 1):
left_max = max(left_max, height[j])
right_max = 0
for j in range(i, n):
right_max = max(right_max, height[j])
water += min(left_max, right_max) - height[i]
return waterחשבו מראש את העמודה הגבוהה ביותר בכל צד
האינטואיציה
העמודה הגבוהה ביותר מ־0 עד i היא הגדולה מבין העמודה הגבוהה ביותר מ־0 עד i-1 ובין height[i]. לכן מעבר אחד משמאל ממלא את leftMax, כשכל איבר נבנה על סמך האיבר שלפניו, ומעבר אחד מימין ממלא את rightMax. מעבר שלישי מיישם את הנוסחה.
עבור [0, 3, 1, 0, 2, 5, 1, 2] הרמות min(leftMax[i], rightMax[i]) הן [0, 3, 3, 3, 3, 5, 2, 2]. לאחר הפחתת הגבהים מתקבל [0, 0, 2, 3, 1, 0, 1, 0], שסכומו 7. זמן הריצה הוא O(n), במחיר של שני מערכים המכילים n מספרים.
אלגוריתם
- מלאו את
leftMaxמשמאל לימין:leftMax[0] = height[0], ואזleftMax[i] = max(leftMax[i-1], height[i]). - מלאו את
rightMaxמימין לשמאל:rightMax[n-1] = height[n-1], ואזrightMax[i] = max(rightMax[i+1], height[i]). - עבור כל אינדקס, הוסיפו את
min(leftMax[i], rightMax[i]) - height[i]לסכום הכולל. - החזירו את הסכום הכולל.
def trap(height):
n = len(height)
left_max = [0] * n # tallest bar from index 0 to i
right_max = [0] * n # tallest bar from index i to n-1
left_max[0] = height[0]
for i in range(1, n):
left_max[i] = max(left_max[i - 1], height[i])
right_max[n - 1] = height[n - 1]
for i in range(n - 2, -1, -1):
right_max[i] = max(right_max[i + 1], height[i])
water = 0
for i in range(n):
water += min(left_max[i], right_max[i]) - height[i]
return waterשני מצביעים שמזיזים את הצד התחתון
האינטואיציה
הנוסחה זקוקה רק לדופן הנמוכה יותר, ושני מצביעים יכולים להוכיח איזו מהן היא. מתחילים את left ב־0 ואת right באינדקס האחרון, עוקבים אחר leftMax ו־rightMax, המייצגים את העמודה הגבוהה ביותר שכל מצביע עבר, כולל העמודה שעליה הוא נמצא, ותמיד מזיזים את המצביע שנמצא על העמודה הנמוכה יותר. אז כל העמודות שכבר עברנו אינן גבוהות יותר מהגבוהה מבין שתי העמודות הנוכחיות, כי מצביע עובר רק על פני עמודה שאינה גבוהה יותר מהעמודה שמתחת למצביע השני.
נניח ש־height[left] < height[right]. לפי אינווריאנט, leftMax הוא לכל היותר height[right], עמודה שנמצאת מימין ל־left. לכן הדופן הימנית של left היא לפחות בגובה leftMax, והמפלס שלה הוא בדיוק leftMax, יהיו אשר יהיו הדברים שנמצאים בין המצביעים. כשהעמודה הימנית נמוכה יותר או שווה בגובהה, הטיעון המקביל מכריע לגבי right.
אלגוריתם
- הגדירו את
left = 0, אתright = n-1, ואתleftMax,rightMaxו-waterכ-0. - כל עוד
left < right: אםheight[left] < height[right], הגדילו אתleftMaxל-height[left]אם צריך, הוסיפו אתleftMax - height[left]והזיזו אתleftימינה. - אחרת, עשו את אותו הדבר בצד ימין והזיזו את
rightשמאלה. - החזירו את
water. העמודה שבה המצביעים נפגשים היא הגבוהה ביותר ואינה מכילה מים.
def trap(height):
left, right = 0, len(height) - 1
left_max = right_max = 0 # tallest bar passed so far from each end
water = 0
while left < right:
if height[left] < height[right]:
# A bar taller than height[left] waits on the right, so left_max sets the level here.
left_max = max(left_max, height[left])
water += left_max - height[left]
left += 1
else:
# A bar at least as tall as height[right] waits on the left, so right_max sets the level.
right_max = max(right_max, height[right])
water += right_max - height[right]
right -= 1
return water
מלכודות ומקרי קצה
- הוספת המים לפני עדכון המקסימום הרץ. כאשר
height[left]גדול מ-leftMax, הכמות שלילית והסכום קטן. - הזזת המצביע על המוט הגבוה יותר, שרמתו לא הוכחה. עבור
[4, 1, 3, 0, 5], הגרסה הזאת מחזירה 4 במקום 8. - התחשבות רק בשכנים הקרובים ביותר. ב-
[3, 0, 2, 0, 1, 0, 4], המוט שגובהו 1 מתמלא עד לגובה 3, שנקבע על ידי מוטות שנמצאים במרחק של ארבעה צעדים ושני צעדים; התשובה היא 12. - התייחסות לקצוות המערך כאל קירות. המים נשפכים מהם, ולכן מוט אחד, שני מוטות או שורה שרק עולה או רק יורדת מכילים 0.
שאלות נפוצות3
מהי סיבוכיות הזמן של בעיית לכידת מי הגשם?
פתרון שני המצביעים פועל בזמן O(n) ומשתמש ב־O(1) מקום נוסף, מכיוון שבכל צעד מצביע אחד נע פנימה. הגרסה עם המערכים leftMax ו־rightMax פועלת גם היא בזמן O(n), אך משתמשת במקום O(n). סריקת שני הצדדים מכל עמודה היא O(n²).
האם אפשר לפתור את בעיית לכידת מי הגשם באמצעות מחסנית?
כן. שמרו מחסנית של אינדקסים עם גבהים יורדים. כשמגיעה עמודה גבוהה יותר, הוציאו את העליונה: היא הרצפה של בריכה שתחומה בקיר החדש ובעמודה הנוכחית. הוסיפו (min(two walls) - floor) × (distance between the walls - 1) והמשיכו להוציא איברים. כך ממלאים את המים בשכבות אופקיות, בזמן ובמקום של O(n).
במה שונה Trapping Rain Water מ-Container With Most Water?
ב־Container With Most Water הקווים אינם תופסים מקום, והתשובה היא המלבן הגדול ביותר בין שני קווים. כאן כל עמודה מלאה, והתשובה היא סכום המים מעל כל העמודות. בשתי הבעיות מזיזים את הצד הנמוך יותר מאותה סיבה: התוצאה שלו כבר נקבעה.
בעיות דומות
בעיות שמשתמשות באותם רעיונות. פתרון של שתיים או שלוש מהן מקבע את הדפוס.
def trap(height):
# כתבו כאן קודבדיקה #1
בדיקה #2
בדיקה #3
ארגומנט
height = [0, 3, 1, 0, 2, 5, 1, 2]
הפלט הצפוי
7