מתכונת 8 במדעי המחשב - יסודות מדעי המחשב 899371
1–2 יח"ל · שאלון 899371 · 6 שאלות · 2:30 שעות · פתרון מלא לכל סעיף
מבחן מתכונת במדעי המחשב, יסודות מדעי המחשב, במבנה של בחינת הבגרות. כל שאלה בו בנויה כמו שאלה מבגרות אחרת. לכל סעיף יש תשובה ופתרון מלא, וכל קוד מופיע ב-Java וב-C#. פתרו בזמן של הבחינה, ורק אחר כך פתחו את הפתרונות.
הוראות הבחינה
א. משך הבחינה: שעתיים וחצי.
ב. מבנה השאלון ומפתח ההערכה: בשאלון זה שני פרקים.
פרק ראשון – (20×2) – 40 נקודות
פרק שני – (30×2) – 60 נקודות
סך הכול – 100 נקודות
ג. חומר עזר מותר בשימוש: כל חומר עזר, חוץ ממחשבון שיש בו אפשרות תכנוּת.
ד. הוראה מיוחדת:
את כל התוכניות שיש לכתוב בשפת מחשב כתבו בשפה אחת בלבד – Java או C#.
הערה: לא יוּרדוּ נקודות אם תכתבו בתוכניות אות גדולה במקום אות קטנה או להפך.
יש לכתוב במחברת הבחינה בלבד. יש לרשום "טיוטה" בראש כל עמוד המשמש טיוטה.
כתיבת טיוטה בדפים שאינם במחברת הבחינה עלולה לגרום לפסילת הבחינה.
השאלות בשאלון זה מנוסחות בלשון רבים, אף על פי כן על כל תלמידה וכל תלמיד להשיב עליהן באופן אישי.
בהצלחה!
השאלות
בשאלון זה שני פרקים.
יש לענות על שאלות משני הפרקים, לפי ההוראות בכל פרק.
הערה: בכל שאלה שנדרשת בה קליטה, אין צורך לבדוק את תקינות הקלט.
לפותרים בשפת Java: בכל שאלה שנדרשת בה קליטה, הניחו שבתוכנית כתובה ההוראה:
Scanner scan = new Scanner (System.in);
פרק ראשון (40 נקודות)
ענו על שתיים מן השאלות 1–3 (לכל שאלה – 20 נקודות).
שאלה 1 · מעקב – השוואת זוגות סימטריים במערך (20 נקודות)
נתונים שני מערכים מטיפוס שלם: a בגודל 10, ו־ b בגודל 5.
המערך a מכיל מספרים גדולים מ־ 0, והמערך b מכיל כולו אפסים.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| a | 9 | 4 | 15 | 6 | 3 | 8 | 3 | 6 | 11 | 2 |
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| b | 0 | 0 | 0 | 0 | 0 |
נתון קטע התכנית שלפניך, הכתוב ב־ Java וב־ C#.
Java
int k = 0;
for(int i=0; i<a.length/2; i++)
{
int j = a.length-1-i;
if(a[i] > a[j])
{
b[k] = a[i] - a[j];
k++;
}
}C#
int k = 0;
for(int i=0; i<a.Length/2; i++)
{
int j = a.Length-1-i;
if(a[i] > a[j])
{
b[k] = a[i] - a[j];
k++;
}
}עקבו בעזרת טבלת מעקב אחר ביצוע קטע התכנית בעבור המערכים a ו־ b הנתונים, ורישמו את המערך b לאחר ביצוע קטע התכנית.
בטבלת המעקב יש לכלול עמודה לכל אחד מהמשתנים האלה: b[k], k, a[j], a[i], j, i
תנו דוגמה מייצגת למערך a בגודל 10 המכיל מספרים גדולים מ־ 0, שבעבורו, לאחר ביצוע קטע התכנית, אף אחד מתאי המערך b לא יכיל את הערך 0.
◂ פתרון מלא לשאלה 1
סעיף 1
b = {7, 9, 3, 0, 0} (k = 3)
מה נדרש: לעקוב בטבלת מעקב אחר ביצוע הקטע על המערכים הנתונים, ולרשום את המערך b בסוף הביצוע.
רעיון: הלולאה עוברת על זוגות סימטריים במערך: התא הראשון עם האחרון, השני עם הלפני־אחרון וכן הלאה. j = a.length-1-i הוא המציין של "בן הזוג" של i. התנאי i < a.length/2 = 5 אומר שיש 5 איטרציות (i = 0..4) – בדיוק חצי מערך, כך שכל זוג נבדק פעם אחת. כאשר האיבר השמאלי גדול מבן הזוג שלו, ההפרש a[i] - a[j] נכתב ל־b במקום k, ו־k גדל ב־1.
פתרון – טבלת מעקב (בעמודה b[k] רשומה ההשמה כשהיא מתבצעת; k אחרי ביצוע גוף הלולאה):
| i | j | a[i] | a[j] | a[i] > a[j] ? | b[k] | k |
|---|---|---|---|---|---|---|
| (התחלה) | 0 | |||||
| 0 | 9 | 9 | 2 | כן | b[0] ← 7 | 1 |
| 1 | 8 | 4 | 11 | לא | – | 1 |
| 2 | 7 | 15 | 6 | כן | b[1] ← 9 | 2 |
| 3 | 6 | 6 | 3 | כן | b[2] ← 3 | 3 |
| 4 | 5 | 3 | 8 | לא | – | 3 |
| 5 | הלולאה מסתיימת (5 < 5 לא מתקיים) | 3 |
המערך b לאחר ביצוע קטע התכנית:
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| b | 7 | 9 | 3 | 0 | 0 |
בדיקה: על המערכים הנתונים מתקבל b = 7 9 3 0 0 ו־k = 3.
טעות נפוצה: להמשיך את הלולאה עד סוף המערך (i עד 9). הלולאה עוצרת באמצע, ולכן כל זוג נבדק פעם אחת בלבד – ו־b בגודל 5 מספיק.
סעיף 2
למשל a = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1} (ואז b = {9, 7, 5, 3, 1}) – כל מערך שבו a[i] > a[9-i] לכל i בין 0 ל־4.
מה נדרש: מערך a בגודל 10 עם מספרים גדולים מ־0, שבעבורו בסוף הביצוע אף תא של b לא יהיה 0.
רעיון: ל־b נכתב ערך רק כאשר a[i] > a[j], והערך שנכתב הוא a[i] - a[j] – מספר חיובי, כלומר שונה מ־0. יש בדיוק 5 איטרציות ו־5 תאים ב־b, ובכל השמה k מתקדם בתא אחד. לכן כל תאי b יתמלאו בערכים שונים מ־0 אם ורק אם התנאי מתקיים בכל 5 האיטרציות: a[0] > a[9], a[1] > a[8], a[2] > a[7], a[3] > a[6], a[4] > a[5].
פתרון: דוגמה מייצגת – מערך יורד:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| a | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
ואז b = 9, 7, 5, 3, 1.
(המערך לא חייב להיות יורד. גם 6, 7, 9, 5, 8, 2, 4, 1, 3, 5 מתאים – כל זוג סימטרי מקיים את התנאי – ואז b = 1, 4, 8, 1, 6.)
בדיקה: בשתי הדוגמאות מתקבל b = 9 7 5 3 1 ו־b = 1 4 8 1 6, ובשתיהן k = 5. לעומת זאת, על המערך העולה 1, 2, …, 10 התקבל b = 0 0 0 0 0.
טעות נפוצה: לחשוב שמספיק ש־a[0] יהיה הגדול ביותר. צריך שהתנאי יתקיים בכל אחד מחמשת הזוגות.
שאלה 2 · כרטיסי מוזיאון – השארת המופע הראשון של כל מספר (20 נקודות)
ממשו את הפעולה keepFirst בשפת Java או KeepFirst בשפת C# :
Java – public static void keepFirst (int[] arr)
C# – public static void KeepFirst (int[] arr)
בכניסה למוזיאון סורקים את כרטיסי המבקרים. במערך arr שמתקבל רשומים מספרי הכרטיסים שנסרקו, לפי סדר הסריקה. כל מספרי הכרטיסים הם מספרים חיוביים. כרטיס עשוי להיסרק יותר מפעם אחת, ולכן מספר עשוי להופיע במערך כמה פעמים.
הפעולה מעבירה לתחילת המערך arr כל מספר כרטיס פעם אחת בלבד, לפי הסדר שבו הוא הופיע לראשונה במערך, ומציבה את הערך -1 בכל התאים שנותרו אחריהם.
דוגמה:
בעבור המערך arr שלפניכם:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| arr | 12 | 7 | 12 | 5 | 7 | 7 | 30 | 5 |
המערך arr ייראה כך בסיום הפעולה:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| arr | 12 | 7 | 5 | 30 | -1 | -1 | -1 | -1 |
הסבר: במערך יש ארבעה מספרי כרטיסים שונים: 12, 7, 5, 30 (לפי הסדר שבו הופיעו לראשונה). הם הועברו לתחילת המערך, ובארבעת התאים הנותרים הוצב -1.
ממשו את הפעולה keepFirst בשפת Java או KeepFirst בשפת C#, לפי התיאור שלמעלה.
◂ פתרון מלא לשאלה 2
פתרון
סורקים את arr לפי הסדר; כל איבר שעדיין לא נמצא במערך העזר (חיפוש סדרתי בחלק שכבר מולא) מועתק אליו. בסוף מעתיקים את k האיברים השונים חזרה ל-arr וממלאים את שאר התאים ב--1.
מה נדרש
לסדר מחדש את arr כך שבתחילתו יופיע כל מספר פעם אחת, לפי סדר ההופעה הראשונה שלו, ובשאר התאים -1. הפעולה לא מחזירה ערך – היא משנה את arr עצמו.
רעיון
משתמשים במערך עזר temp באותו גודל ובמונה k (כמה מספרים שונים כבר הועתקו ל-temp):
מכיוון שסורקים את arr לפי הסדר, כל מספר נכנס ל-temp ברגע ההופעה הראשונה שלו, ולכן הסדר נשמר.
פתרון
Java
public static void keepFirst(int[] arr)
{
int[] temp = new int[arr.length];
int k = 0; // כמה מספרים שונים כבר הועתקו ל-temp
for (int i = 0; i < arr.length; i++)
{
// חיפוש סדרתי: האם arr[i] כבר נמצא ב-temp?
boolean found = false;
for (int j = 0; j < k && !found; j++)
{
if (temp[j] == arr[i])
found = true;
}
if (!found)
{
temp[k] = arr[i];
k++;
}
}
// העתקה חזרה ל-arr והשלמת התאים הנותרים ב-1-
for (int i = 0; i < arr.length; i++)
{
if (i < k)
arr[i] = temp[i];
else
arr[i] = -1;
}
}C#
public static void KeepFirst(int[] arr)
{
int[] temp = new int[arr.Length];
int k = 0; // כמה מספרים שונים כבר הועתקו ל-temp
for (int i = 0; i < arr.Length; i++)
{
// חיפוש סדרתי: האם arr[i] כבר נמצא ב-temp?
bool found = false;
for (int j = 0; j < k && !found; j++)
{
if (temp[j] == arr[i])
found = true;
}
if (!found)
{
temp[k] = arr[i];
k++;
}
}
// העתקה חזרה ל-arr והשלמת התאים הנותרים ב-1-
for (int i = 0; i < arr.Length; i++)
{
if (i < k)
arr[i] = temp[i];
else
arr[i] = -1;
}
}בדיקה
על מערך הדוגמה {12, 7, 12, 5, 7, 7, 30, 5}:
גם המקרים {4, 4, 4} ← {4, -1, -1}, {1, 2, 3} ← ללא שינוי, {9} ← {9} ו-{3, 1, 3, 2, 1, 2} ← {3, 1, 2, -1, -1, -1} עובדים.
טעות נפוצה: לחפש את arr[i] בכל המערך arr (ולא רק בחלק שכבר נבנה) – כך כל מספר שמופיע פעמיים "נמצא" כבר בהופעה הראשונה שלו ולא נכנס כלל. טעות נוספת: לשכוח למלא את התאים הנותרים ב--1 – אז נשארים בהם הערכים הישנים.
public static void keepFirst(int[] arr)
{
int[] temp = new int[arr.length];
int k = 0; // כמה מספרים שונים כבר הועתקו ל-temp
for (int i = 0; i < arr.length; i++)
{
// חיפוש סדרתי: האם arr[i] כבר נמצא ב-temp?
boolean found = false;
for (int j = 0; j < k && !found; j++)
{
if (temp[j] == arr[i])
found = true;
}
if (!found)
{
temp[k] = arr[i];
k++;
}
}
// העתקה חזרה ל-arr והשלמת התאים הנותרים ב-1-
for (int i = 0; i < arr.length; i++)
{
if (i < k)
arr[i] = temp[i];
else
arr[i] = -1;
}
}שאלה 3 · מספר מרגל – סכום הספרות שווה למכפלת הספרות (20 נקודות)
מספר חיובי – num ייקרא "מספר מרגל" אם מתקיים התנאי שלפניכם:
סכום כל הספרות של num שווה למכפלת כל הספרות של num .
דוגמה ל"מספר מרגל": המספר 1124 , כי סכום הספרות שלו הוא 1 + 1 + 2 + 4 = 8 , ומכפלת הספרות שלו היא 1 · 1 · 2 · 4 = 8 .
דוגמה נוספת ל"מספר מרגל": המספר 123 , כי 1 + 2 + 3 = 6 וגם 1 · 2 · 3 = 6 .
דוגמה למספר שאינו "מספר מרגל": המספר 1234 , כי סכום הספרות שלו הוא 1 + 2 + 3 + 4 = 10 , ואילו מכפלת הספרות שלו היא 1 · 2 · 3 · 4 = 24 .
כתבו פעולה חיצונית ששמה isSpy בשפת Java או IsSpy בשפת C#, המקבלת מספר מטיפוס שלם – num הגדול מ־ 0 . הפעולה מחזירה true אם המספר הוא "מספר מרגל", אחרת היא מחזירה false .
כתבו פעולה חיצונית ששמה printSpies בשפת Java או PrintSpies בשפת C#, המקבלת מספר מטיפוס שלם – n הגדול מ־ 0 . הפעולה מדפיסה את כל "מספרי המרגל" שיש מ־ 1 ועד n (כולל).
הערה: יש להשתמש בפעולה שכתבתם בסעיף א.
◂ פתרון מלא לשאלה 3
סעיף א
מעבר אחד על הספרות של עותק של num (% 10, / 10): sum מתחיל ב-0 ומוסיפים לו כל ספרה, mult מתחיל ב-1 וכופלים אותו בכל ספרה; מחזירים sum == mult.
מה נדרש
פעולה בוליאנית שבודקת אם סכום הספרות של num שווה למכפלת הספרות שלו.
רעיון
פתרון
Java
public static boolean isSpy(int num)
{
int sum = 0;
int mult = 1;
int temp = num; // עובדים על עותק, כדי לא להרוס את num
while (temp > 0)
{
int digit = temp % 10;
sum = sum + digit;
mult = mult * digit;
temp = temp / 10;
}
return sum == mult;
}C#
public static bool IsSpy(int num)
{
int sum = 0;
int mult = 1;
int temp = num; // עובדים על עותק, כדי לא להרוס את num
while (temp > 0)
{
int digit = temp % 10;
sum = sum + digit;
mult = mult * digit;
temp = temp / 10;
}
return sum == mult;
}בדיקה
isSpy(1124) = true, isSpy(123) = true, isSpy(22) = true, isSpy(7) = true, isSpy(1412) = true; isSpy(1234) = false, isSpy(10) = false, isSpy(105) = false, isSpy(2222) = false, isSpy(99) = false.
טעות נפוצה: לאתחל את mult ב-0 – אז המכפלה תישאר 0 תמיד. טעות נוספת: לבדוק אם המספר עצמו שווה לסכום או למכפלה – ההשוואה היא בין הסכום למכפלה.
public static boolean isSpy(int num)
{
int sum = 0;
int mult = 1;
int temp = num; // עובדים על עותק, כדי לא להרוס את num
while (temp > 0)
{
int digit = temp % 10;
sum = sum + digit;
mult = mult * digit;
temp = temp / 10;
}
return sum == mult;
}סעיף ב
לולאה i = 1..n, מדפיסים i אם isSpy(i). למשל עד 2000: 1–9, 22, 123, 132, 213, 231, 312, 321, 1124, 1142, 1214, 1241, 1412, 1421.
מה נדרש
להדפיס את כל "מספרי המרגל" מ-1 עד n, תוך שימוש ב-isSpy מסעיף א.
רעיון
לולאה על i מ-1 עד n (כולל); לכל i קוראים ל-isSpy(i), ואם התשובה true – מדפיסים את i.
פתרון
Java
public static void printSpies(int n)
{
for (int i = 1; i <= n; i++)
{
if (isSpy(i))
{
System.out.println(i);
}
}
}C#
public static void PrintSpies(int n)
{
for (int i = 1; i <= n; i++)
{
if (IsSpy(i))
{
Console.WriteLine(i);
}
}
}בדיקה
שימו לב: כל מספר חד-ספרתי הוא "מספר מרגל" (הסכום והמכפלה של ספרה אחת שווים לה עצמה), ולכן ההדפסה תמיד מתחילה ב-1.
printSpies(2000) מדפיסה (כל מספר בשורה נפרדת):
1, 2, 3, 4, 5, 6, 7, 8, 9, 22, 123, 132, 213, 231, 312, 321, 1124, 1142, 1214, 1241, 1412, 1421
ו־printSpies(1) מדפיסה רק 1. התוצאות זהות לבדיקה עצמאית שכתבנו בנפרד (פירוק המספר כמחרוזת ספרות).
public static void printSpies(int n)
{
for (int i = 1; i <= n; i++)
{
if (isSpy(i))
{
System.out.println(i);
}
}
}פרק שני (60 נקודות)
ענו על שתיים מן השאלות 4–6 (לכל שאלה – 30 נקודות).
שאלה 4 · תחנת מדידה בנחל – ימי שיא ושפל
נתונה המחלקה RiverGauge , המייצגת תחנה המודדת את גובה פני המים בנחל.
למחלקה שתי תכונות:
הניחו שקיימות פעולות get/Get ו־ set/Set עבור תכונות המחלקה.
"שיא" הוא יום שבו גובה המים היה גבוה גם מגובה המים ביום הקודם וגם מגובה המים ביום שאחריו.
"שפל" הוא יום שבו גובה המים היה נמוך גם מגובה המים ביום הקודם וגם מגובה המים ביום שאחריו.
היום הראשון והיום האחרון בתקופת המדידה אינם נחשבים "שיא" או "שפל".
כתבו פעולה פנימית ששמה countTurns בשפת Java או CountTurns בשפת C# , המקבלת פרמטר – peak מטיפוס בוליאני, ומחזירה מספר שלם בהתאם לאפיון שלפניכם:
לדוגמה, עבור המערך levels שלפניכם, אם peak הוא true הפעולה תחזיר 3 , ואם peak הוא false הפעולה תחזיר 1 .
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
|---|---|---|---|---|---|---|---|---|---|
| levels | 120 | 135 | 128 | 128 | 140 | 150 | 131 | 133 | 120 |
הסבר: התאים באינדקסים 7 , 5 , 1 מייצגים ימים שנחשבים "שיא". התא באינדקס 6 מייצג יום שנחשב "שפל".
התא באינדקס 2 אינו "שפל", כי גובה המים בו (128) אינו נמוך מגובה המים ביום שאחריו (128).
נחל ייקרא "סוער" אם מספר הימים שנחשבים "שיא" או "שפל" (יחד) גדול ממחצית מספר ימי המדידה.
כתבו פעולה פנימית ששמה isRestless בשפת Java או IsRestless בשפת C# , המחזירה true אם הנחל "סוער". אחרת הפעולה תחזיר false .
למשל, בדוגמה שלעיל הפעולה תחזיר false , כיוון שיש 4 ימים שנחשבים "שיא" או "שפל", ו־ 4 אינו גדול ממחצית מספר ימי המדידה (9 ימים).
חובה להיעזר בפעולה שכתבתם בסעיף א.
◂ פתרון מלא לשאלה 4
סעיף א
לכל i מ־1 עד levels.length − 2: אם peak ו־levels[i] גדול משני שכניו – סופרים; אם !peak ו־levels[i] קטן משני שכניו – סופרים. בדוגמה: 3 ו־1.
מה נדרש: פעולה פנימית במחלקה RiverGauge שסופרת את ימי ה"שיא" (peak = true) או את ימי ה"שפל" (peak = false).
רעיון: יום i הוא "שיא" אם levels[i] > levels[i-1] וגם levels[i] > levels[i+1], ו"שפל" אם levels[i] < levels[i-1] וגם levels[i] < levels[i+1]. לכל יום צריך גם את היום הקודם וגם את היום הבא, ולכן הלולאה רצה מ־1 עד levels.length - 2 (בלי היום הראשון והאחרון – כך גם אין חריגה מגבולות המערך). סופרים רק את סוג הימים שהפרמטר מבקש.
פתרון: הקוד המלא – בלשונית Java/C# (שתי הפעולות של סעיפים א ו־ב כתובות בתוך המחלקה RiverGauge).
בדיקה: על המערך שבשאלה:
| i | levels[i-1] , levels[i] , levels[i+1] | סוג |
|---|---|---|
| 1 | 120 , 135 , 128 | שיא |
| 2 | 135 , 128 , 128 | – |
| 3 | 128 , 128 , 140 | – |
| 4 | 128 , 140 , 150 | – |
| 5 | 140 , 150 , 131 | שיא |
| 6 | 150 , 131 , 133 | שפל |
| 7 | 131 , 133 , 120 | שיא |
מתקבל countTurns(true) = 3 ו־countTurns(false) = 1, כמו בשאלה. עבור מערך עולה (5 , 6 , 7 , 8) התקבל 0 לשני הסוגים, ועבור מערך של יום אחד – 0.
טעות נפוצה: להריץ את הלולאה מ־0 או עד levels.length - 1 ולגשת ל־levels[i-1] או ל־levels[i+1] מחוץ לגבולות המערך; או להשתמש ב־>= ולספור את יום 2 בדוגמה (128 , 128).
// סעיף א
public int countTurns(boolean peak)
{
int count = 0;
for (int i = 1; i < levels.length - 1; i++) // בלי היום הראשון והיום האחרון
{
if (peak && levels[i] > levels[i - 1] && levels[i] > levels[i + 1]) // "שיא"
count++;
else if (!peak && levels[i] < levels[i - 1] && levels[i] < levels[i + 1]) // "שפל"
count++;
}
return count;
}
// סעיף ב
public boolean isRestless()
{
int turns = countTurns(true) + countTurns(false);
return turns * 2 > levels.length; // יותר ממחצית ימי המדידה
}סעיף ב
int turns = countTurns(true) + countTurns(false); return turns * 2 > levels.length;
מה נדרש: פעולה פנימית שמחזירה true אם מספר ימי ה"שיא" וה"שפל" יחד גדול ממחצית מספר ימי המדידה, תוך שימוש בפעולה מסעיף א.
רעיון: countTurns(true) + countTurns(false) הוא מספר הימים שהם "שיא" או "שפל" (יום לא יכול להיות גם וגם). "גדול ממחצית" – בודקים turns * 2 > levels.length, וכך אין צורך בחילוק (ואין חשש מחילוק שלמים שמעגל כלפי מטה). אפשר גם לכתוב turns > levels.length / 2.0.
פתרון:
public boolean isRestless()
{
int turns = countTurns(true) + countTurns(false);
return turns * 2 > levels.length;
}בדיקה: בדוגמה 4 · 2 = 8 > 9 לא מתקיים → false. עבור 50 , 60 , 40 , 70 , 30 , 55 יש 2 "שיא" ו־2 "שפל": 8 > 6 → true. עבור 10 , 20 , 10 , 20 יש 2 ימים מיוחדים מתוך 4 – בדיוק מחצית, לא יותר → false.
טעות נפוצה: להשתמש ב־>= – כשמספר הימים המיוחדים שווה בדיוק למחצית, הנחל אינו "סוער".
// סעיף א
public int countTurns(boolean peak)
{
int count = 0;
for (int i = 1; i < levels.length - 1; i++) // בלי היום הראשון והיום האחרון
{
if (peak && levels[i] > levels[i - 1] && levels[i] > levels[i + 1]) // "שיא"
count++;
else if (!peak && levels[i] < levels[i - 1] && levels[i] < levels[i + 1]) // "שפל"
count++;
}
return count;
}
// סעיף ב
public boolean isRestless()
{
int turns = countTurns(true) + countTurns(false);
return turns * 2 > levels.length; // יותר ממחצית ימי המדידה
}שאלה 5 · כרטיסים ומושבים – מחלקה מורכבת (30 נקודות)
נתונה המחלקה Seat – מושב באולם מופעים, ולה שתי תכונות:
הניחו שקיימות פעולות get/Get ו־ set/Set לתכונות המחלקה.
שני מושבים הם צמודים אם הם באותה שורה ומספרי המושבים שלהם עוקבים (ההפרש ביניהם הוא 1 בדיוק).
כתבו במחלקה Seat פעולה פנימית ששמה isNextTo בשפת Java או IsNextTo בשפת C# המקבלת עצם אחר – other מטיפוס Seat .
הפעולה מחזירה true אם שני המושבים צמודים, אחרת היא מחזירה false .
למשל, המושב בשורה 7 מספר 12 והמושב בשורה 7 מספר 13 צמודים; המושב בשורה 7 מספר 12 והמושב בשורה 8 מספר 13 אינם צמודים.
אולם המופעים העירוני מוכר כרטיסים במערכת ממוחשבת. לשם כך נבנתה המחלקה Ticket – כרטיס, ולה שלוש תכונות:
בכל אגף יש מספור שורות ומושבים משלו. הניחו שקיימות פעולות get/Get ו־ set/Set לתכונות המחלקה.
כתבו במחלקה Ticket פעולה פנימית ששמה sameSection בשפת Java או SameSection בשפת C# , המקבלת עצם אחר – other מטיפוס Ticket . הפעולה מחזירה true אם שני הכרטיסים הם לאותו אגף. אחרת הפעולה מחזירה false .
שני כרטיסים "יושבים יחד" אם גם הם לאותו אגף וגם המושבים שלהם צמודים (אם רק תנאי אחד מתקיים, הכרטיסים אינם "יושבים יחד").
כתבו במחלקה Ticket פעולה פנימית ששמה sitTogether בשפת Java או SitTogether בשפת C# , המקבלת עצם אחר – other מטיפוס Ticket . הפעולה מחזירה true אם שני הכרטיסים "יושבים יחד", אחרת הפעולה מחזירה false .
יש להשתמש בפעולות שכתבתם בסעיף א ובתת־סעיף ב(1).
כרטיס נקרא "כרטיס אמצעי" אם יש במערך שני כרטיסים אחרים ש"יושבים יחד" איתו (כפי שהוגדר בתת־סעיף ב(2)), כלומר יש לו שכנים משני צדדיו.
כתבו פעולה חיצונית ששמה printMiddle בשפת Java או PrintMiddle בשפת C# המקבלת מערך – tickets מטיפוס Ticket של כל הכרטיסים שנמכרו למופע מסוים. המערך אינו מסודר לפי סדר כלשהו, אין בו ערכי null , ואין בו שני כרטיסים לאותו מושב באותו אגף. הפעולה תדפיס את הקוד של הקונה של כל "כרטיס אמצעי" במערך (כל הדפסה בשורה חדשה).
יש להשתמש בפעולה שכתבתם בתת־סעיף ב(2).
◂ פתרון מלא לשאלה 5
סעיף א
return row == other.getRow() && Math.abs(number - other.getNumber()) == 1;
מה נדרש: פעולה פנימית ב־ Seat הבודקת אם שני מושבים צמודים.
רעיון: שני תנאים שחייבים להתקיים יחד (&&): אותה שורה, וההפרש בין מספרי המושבים הוא 1 בדיוק. ההפרש יכול להיות 1 או −1 (המושב השני משמאל או מימין), ולכן משתמשים בערך מוחלט: Math.abs (ב־ C#: Math.Abs).
פתרון: הקוד המלא – בלשונית Java / C#.
בדיקה: 7/12 מול 7/13 → true; 7/13 מול 7/12 → true; 7/12 מול 7/14 → false; 7/12 מול 8/13 → false; 7/12 מול 7/12 → false.
טעות נפוצה: לבדוק רק number − other.getNumber() == 1 – כך מושב שנמצא משמאל לא ייחשב צמוד.
public boolean isNextTo(Seat other)
{
return this.row == other.getRow() &&
Math.abs(this.number - other.getNumber()) == 1;
}סעיף ב(1)
return section == other.getSection();
מה נדרש: פעולה פנימית ב־ Ticket הבודקת אם שני כרטיסים הם לאותו אגף.
רעיון: section הוא מטיפוס char, ולכן משווים אותו ישירות עם == ל־ other.getSection().
פתרון: הקוד המלא – בלשונית Java / C#.
בדיקה: אגף B מול אגף B → true; אגף B מול אגף C → false.
public boolean sameSection(Ticket other)
{
return this.section == other.getSection();
}סעיף ב(2)
return sameSection(other) && seat.isNextTo(other.getSeat());
מה נדרש: פעולה sitTogether המחזירה true רק אם הכרטיסים לאותו אגף וגם המושבים צמודים.
רעיון: זו מחלקה מורכבת – התכונה seat היא עצם מטיפוס Seat. את בדיקת הצמידות מעבירים לפעולה isNextTo של העצם seat:
this.seat.isNextTo(other.getSeat()), ומחברים עם sameSection(other) ב־ &&. בדיקת האגף הכרחית: שורה 7 מושב 12 באגף B ושורה 7 מושב 13 באגף C אינם צמודים באמת.
פתרון: הקוד המלא – בלשונית Java / C#.
בדיקה: B 7/12 מול B 7/13 → true; B 7/12 מול C 7/13 → false (אגף שונה); B 7/12 מול B 6/13 → false (שורה שונה).
public boolean sitTogether(Ticket other)
{
return this.sameSection(other) &&
this.seat.isNextTo(other.getSeat());
}סעיף ג
לכל כרטיס i סופרים j ≠ i עם tickets[i].sitTogether(tickets[j]); אם המונה 2 – מדפיסים getBuyerCode()
מה נדרש: להדפיס את הקוד של הקונה של כל כרטיס שיש לו שני שכנים "יושבים יחד" במערך.
רעיון: לולאה כפולה עם מונה. לכל כרטיס i מאפסים מונה, עוברים על כל הכרטיסים j, ואם j ≠ i ו־ tickets[i].sitTogether(tickets[j]) – מגדילים את המונה. בסוף הלולאה הפנימית, אם המונה שווה ל־ 2 – מדפיסים את getBuyerCode() של כרטיס i.
מכיוון שאין שני כרטיסים לאותו מושב, לכל כרטיס יש לכל היותר 2 שכנים (משמאל ומימין), ולכן הבדיקה == 2 שקולה ל־ >= 2.
(התנאי i != j אינו הכרחי כאן, כי מושב אינו צמוד לעצמו – ההפרש 0 – אבל הוא מבהיר את הכוונה.)
פתרון: הקוד המלא – בלשונית Java / C#.
בדיקה: מערך של 8 כרטיסים: 4101 = B 7/12, 4102 = B 7/13, 4103 = B 7/11, 4104 = C 7/14, 4105 = B 3/5, 4106 = B 3/6, 4107 = B 3/7, 4108 = A 7/12. לכרטיס 4101 שני שכנים (4102 ו־ 4103), לכרטיס 4106 שני שכנים (4105 ו־ 4107). לכרטיס 4102 שכן אחד בלבד – 4104 נמצא באגף אחר. מודפסים (כל אחד בשורה): 4101, 4106.
טעות נפוצה: להתחיל את הלולאה הפנימית מ־ j = i + 1 – כך לא נספרים השכנים שמופיעים במערך לפני i, ו"כרטיס אמצעי" עלול לא להיות מודפס.
public static void printMiddle(Ticket[] tickets)
{
for (int i = 0; i < tickets.length; i++)
{
int neighbors = 0;
for (int j = 0; j < tickets.length; j++)
{
if (i != j && tickets[i].sitTogether(tickets[j]))
neighbors++;
}
if (neighbors == 2)
System.out.println(tickets[i].getBuyerCode());
}
}שאלה 6 · שורש המכפלה (30 נקודות)
נתונה פעולה חיצונית ששמה digitProduct בשפת Java או DigitProduct בשפת C# , המקבלת מספר שלם שאינו שלילי – num ומחזירה את מכפלת כל הספרות במספר. אפשר להשתמש בפעולה בלי לממש אותה.
לדוגמה: עבור num = 4448 , הפעולה תחזיר 512 (4 × 4 × 4 × 8) .
"שורש המכפלה" של מספר כלשהו הוא מספר חד־ספרתי המתקבל באופן שלהלן:
מחשבים את מכפלת הספרות שוב ושוב עד שמתקבל מספר חד־ספרתי.
דוגמאות:
"שורש המכפלה" של המספר 7 הוא 7 .
"שורש המכפלה" של המספר 25 הוא 0 (2 × 5 = 10 , ואז 1 × 0 = 0) .
"שורש המכפלה" של המספר 39 הוא 4 , כמפורט להלן:
3 × 9 = 27
2 × 7 = 14
1 × 4 = 4
כתבו פעולה חיצונית ששמה productRoot בשפת Java או ProductRoot בשפת C# המקבלת מספר שלם num שאינו שלילי ומחזירה את "שורש המכפלה" שלו.
"שורש המכפלה" של כל מספר שיש בו את הספרה 0 הוא 0 , אבל גם מספרים רבים שאין בהם את הספרה 0 מגיעים ל"שורש מכפלה" 0 (למשל 25 שבדוגמה שלעיל). יש הטוענים כי בטווח שבין 1 ל־ 99999 , ליותר מ־ 80 אחוזים מן המספרים יש "שורש מכפלה" 0 .
כתבו פעולה חיצונית ששמה isCorrect בשפת Java או IsCorrect בשפת C# המחזירה true אם הטענה נכונה, ואחרת מחזירה false .
הערה: חובה להיעזר בפעולה שכתבתם בסעיף א(1).
נתונה פעולה חיצונית נוספת, ששמה countDigit בשפת Java או CountDigit בשפת C# , המקבלת מספר שלם חיובי – num וסִפרה – digit . הפעולה מחזירה את מספר הפעמים שהספרה digit מופיעה במספר num (ו־ 0 אם אינה מופיעה בו).
אפשר להשתמש בפעולה בלי לממש אותה.
כתבו פעולה חיצונית ששמה isStronger בשפת Java או IsStronger בשפת C# המקבלת שני מספרים שלמים חיוביים: num1 ו־ num2 . הפעולה מחזירה true אם "שורש המכפלה" של num1 מופיע במספר num2 יותר פעמים מאשר "שורש המכפלה" של num2 מופיע במספר num1 , אחרת היא מחזירה false .
למשל עבור num1 = 39 ו־ num2 = 4448 , הפעולה תחזיר true כי "שורש המכפלה" של num1 (4) מופיע במספר num2 (4448) שלוש פעמים, ו"שורש המכפלה" של num2 (0) אינו מופיע במספר num1 (39) כלל.
הערה: חובה להיעזר בפעולה שכתבתם בסעיף א(1).
◂ פתרון מלא לשאלה 6
סעיף א(1)
while (num >= 10) num = digitProduct(num); return num;
מה נדרש: לחשב את "שורש המכפלה" – להפעיל את digitProduct שוב ושוב עד שמתקבל מספר חד־ספרתי.
רעיון: לולאת while: כל עוד num אינו חד־ספרתי (num >= 10) מחליפים אותו במכפלת ספרותיו. מספר חד־ספרתי (כולל 0) מוחזר כמו שהוא – הלולאה לא מתבצעת אף פעם.
פתרון:
public static int productRoot(int num)
{
while (num >= 10)
{
num = digitProduct(num);
}
return num;
}בדיקה: productRoot(7) = 7, productRoot(39) = 4, productRoot(25) = 0, productRoot(4448) = 0 (512 → 10 → 0), productRoot(77) = 8 (49 → 36 → 18 → 8), productRoot(0) = 0.
טעות נפוצה: תנאי num > 10 – עבור 10 הלולאה לא מתבצעת ומוחזר 10, שאינו חד־ספרתי.
public static int productRoot(int num)
{
while (num >= 10)
{
num = digitProduct(num);
}
return num;
}סעיף א(2)
סופרים כמה i בין 1 ל־ 99999 מקיימים productRoot(i) == 0 ומחזירים count·100 > 99999·80. התוצאה: 82401 מספרים (כ־ 82.4%), ולכן הפעולה מחזירה true.
מה נדרש: לבדוק את הטענה על כל המספרים 1 עד 99999 ולהחזיר true אם ליותר מ־ 80% מהם "שורש מכפלה" 0.
רעיון: עוברים בלולאה על כל i מ־ 1 עד 99999, ומונים את המספרים שעבורם productRoot(i) == 0 (סעיף א(1)). "יותר מ־ 80 אחוזים" פירושו countZero / 99999 > 0.8. כדי להימנע מחילוק (ומחילוק בשלמים שמאבד את השבר) כופלים את שני האגפים: countZero × 100 > 99999 × 80. הערכים כאן (עד כ־ 10 מיליון) נכנסים בטיפוס int.
פתרון:
public static boolean isCorrect()
{
int countZero = 0;
for (int i = 1; i <= 99999; i++)
{
if (productRoot(i) == 0)
countZero++;
}
return countZero * 100 > 99999 * 80;
}בדיקה: מתקבל countZero = 82401 מתוך 99999 (כ־ 82.4%), ולכן הפעולה מחזירה true – הטענה נכונה.
טעות נפוצה: לכתוב countZero / 99999 > 0.8 – בחילוק בשלמים המנה היא 0, והתוצאה תמיד false.
public static boolean isCorrect()
{
int countZero = 0;
for (int i = 1; i <= 99999; i++)
{
if (productRoot(i) == 0)
countZero++;
}
return countZero * 100 > 99999 * 80;
}סעיף ב
return countDigit(num2, productRoot(num1)) > countDigit(num1, productRoot(num2));
מה נדרש: להשוות בין מספר ההופעות של שורש המכפלה של כל מספר בתוך המספר השני.
רעיון:
פתרון:
public static boolean isStronger(int num1, int num2)
{
int count1 = countDigit(num2, productRoot(num1));
int count2 = countDigit(num1, productRoot(num2));
return count1 > count2;
}בדיקה: isStronger(39, 4448) → true (3 > 0); isStronger(4448, 39) → false (0 > 3 לא מתקיים); isStronger(77, 88) → true (השורש של 77 הוא 8 ומופיע פעמיים ב־ 88; השורש של 88 הוא 8 ואינו מופיע ב־ 77); isStronger(48, 84) → false (שני השורשים 6, ואינם מופיעים – 0 מול 0).
טעות נפוצה: להחליף את המספרים בקריאה – countDigit(num1, productRoot(num1)) – כלומר לחפש את השורש של מספר בתוך אותו מספר עצמו.
public static boolean isStronger(int num1, int num2)
{
int count1 = countDigit(num2, productRoot(num1));
int count2 = countDigit(num1, productRoot(num2));
return count1 > count2;
}טבלת התשובות
| שאלה | סעיף | תשובה |
|---|---|---|
| 1 | 1 | b = {7, 9, 3, 0, 0} (k = 3) |
| 2 | למשל a = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1} (ואז b = {9, 7, 5, 3, 1}) – כל מערך שבו a[i] > a[9-i] לכל i בין 0 ל־4. | |
| 2 | - | סורקים את arr לפי הסדר; כל איבר שעדיין לא נמצא במערך העזר (חיפוש סדרתי בחלק שכבר מולא) מועתק אליו. בסוף מעתיקים את k האיברים השונים חזרה ל-arr וממלאים את שאר התאים ב--1. |
| 3 | א | מעבר אחד על הספרות של עותק של num (% 10, / 10): sum מתחיל ב-0 ומוסיפים לו כל ספרה, mult מתחיל ב-1 וכופלים אותו בכל ספרה; מחזירים sum == mult. |
| ב | לולאה i = 1..n, מדפיסים i אם isSpy(i). למשל עד 2000: 1–9, 22, 123, 132, 213, 231, 312, 321, 1124, 1142, 1214, 1241, 1412, 1421. | |
| 4 | א | לכל i מ־1 עד levels.length − 2: אם peak ו־levels[i] גדול משני שכניו – סופרים; אם !peak ו־levels[i] קטן משני שכניו – סופרים. בדוגמה: 3 ו־1. |
| ב | int turns = countTurns(true) + countTurns(false); return turns * 2 > levels.length; | |
| 5 | א | return row == other.getRow() && Math.abs(number - other.getNumber()) == 1; |
| ב(1) | return section == other.getSection(); | |
| ב(2) | return sameSection(other) && seat.isNextTo(other.getSeat()); | |
| ג | לכל כרטיס i סופרים j ≠ i עם tickets[i].sitTogether(tickets[j]); אם המונה 2 – מדפיסים getBuyerCode() | |
| 6 | א(1) | while (num >= 10) num = digitProduct(num); return num; |
| א(2) | סופרים כמה i בין 1 ל־ 99999 מקיימים productRoot(i) == 0 ומחזירים count·100 > 99999·80. התוצאה: 82401 מספרים (כ־ 82.4%), ולכן הפעולה מחזירה true. | |
| ב | return countDigit(num2, productRoot(num1)) > countDigit(num1, productRoot(num2)); |