Path Sum
لديك شجرة ثنائية مخزّنة في المصفوفة tree بترتيب المستويات، ورقم targetSum. يقع الجذر عند الفهرس 0، ويقع ابنا العقدة عند الفهرس i عند 2*i+1 (الأيسر) و2*i+2 (الأيمن)، وتشير -1 إلى موضع فارغ، وقد تنتهي المصفوفة بعناصر -1 إضافية. أرجِع true إذا كان هناك مسار من الجذر نزولًا إلى ورقة تكون قيمه مجتمعة مساوية لـ targetSum، وإلا فأرجِع false. الورقة هي عقدة يكون موضعا ابنيها فارغين.
الدالة
- treeinteger-array
- الشجرة الثنائية بترتيب المستويات، مع استخدام -1 للدلالة على موضع فارغ
- targetSuminteger
- المجموع الذي يجب أن يبلغه المسار من الجذر إلى الورقة
- تُرجعboolean
- صحيح إذا كان مجموع إحدى المسارات من الجذر إلى الورقة يساوي targetSum، وخطأ خلاف ذلك
القيود
1 ≤ tree.length ≤ 32767- كل
tree[i]إما-1أو قيمة تحقق0 ≤ tree[i] ≤ 1000. tree[0]لا تساوي أبدًا-1، لذا تحتوي الشجرة على عقدة واحدة على الأقل.- قد تنتهي المصفوفة بعناصر إضافية
-1بعد العقدة الأخيرة. - كلا الابنين في الموضع الفارغ فارغان أيضًا، والعمق لا يتجاوز
14. 0 ≤ targetSum ≤ 15000
أمثلة
- المدخلات
- tree = [3, 9, 6, -1, 2, 1, 7]targetSum = 14
- المخرجات
- true
- الشرح
- المسار
3،9،2(الفهارس0،1،4) مجموعها14، و2عند الفهرس4عقدة ورقية.
- المدخلات
- tree = [3, 9, 6, -1, 2, 1, 7]targetSum = 12
- المخرجات
- false
- الشرح
3 + 9 = 12، لكن9له ابن، لذا لا ينتهي أي مسار عنده. مجموع المسارات الثلاثة من الجذر إلى الورقة هو14و10و16، وليس أيٌّ منها12.
- المدخلات
- tree = [4, -1, -1]targetSum = 4
- المخرجات
- true
- الشرح
- الموضعان المخصصان لابني الجذر فارغان، لذا فإن الجذر ورقة بمفرده. مجموع المسار الذي يحتوي على
4فقط هو4.
سؤال إضافي
هل يمكنك عدّ المسارات التي يكون مجموعها targetSum إذا كان من الممكن أن يبدأ المسار عند أي عقدة وينتهي عند أي عقدة أسفلها؟
تلميحات
افتحها واحدًا تلو الآخر. كل تلميح يكشف أكثر قليلًا.
انزل من الجذر واحتفظ بمجموع جارٍ. أين يُسمح لك بمقارنة هذا المجموع مع
targetSum؟فقط عند ورقة، أي عقدة يكون موضعا ابنيها فارغين. العقدة التي لها ابن واحد لا تنهي مسارًا، حتى لو كان المجموع قد طابق المطلوب بالفعل. مرّر مجموع المسار حتى الآن إلى كل ابن.
احتفظ بمكدس من الأزواج: فهرس عقدة والمجموع من الجذر إلى تلك العقدة. أخرج زوجًا من المكدس؛ إذا كانت العقدة ورقة وكان المجموع يساوي
targetSum، فأعِدtrue. وإلا فأضف كل ابن موجود إلى المكدس مع المجموع مضافًا إليه قيمة الابن.
الحل
لا تُحتسب إلا المسارات الكاملة، من الجذر نزولًا إلى ورقة. قد يصل المجموع الجاري إلى targetSum عند عقدة لا تزال لها أبناء، وهذا لا يُحتسب. لذا انقل مجموع المسار إلى كل عقدة، كمعامل أو على مكدس، وقارنه بالهدف عند الأوراق فقط.
الاستدعاء الذاتي للمجموع المتبقي
الفكرة
يبدأ المسار الذي يكون مجموعه targetSum بقيمة الجذر، لذا يجب أن يكون مجموع ما تبقى منه، والذي يبدأ عند أحد أبناء الجذر، مساويًا لـ targetSum ناقص تلك القيمة. هذا هو السؤال نفسه على شجرة أصغر. اطرح قيمة كل عقدة أثناء النزول؛ وعند ورقة ينتهي المسار، لذا تكون الإجابة هي ما إذا لم يتبقَّ شيء.
في المثال الأول، يتبقى من الجذر 14 - 3 = 11، ويتبقى من 9 مقدار 2، ومن الورقة 2 يتبقى 0. في المثال الثاني، يتبقى من 9 مقدار 0 بالفعل، لكن له ابن، لذا يستمر البحث. تُزار كل عقدة مرة واحدة، بزمن O(n)، ويحتفظ مكدس الاستدعاءات بإطار واحد لكل مستوى، بحد أقصى 15.
الخوارزمية
- اكتب
walk(i, remaining)واطرحtree[i]منremaining. - إذا كان موضعا الابنين لـ
iفارغين (الفهرس يتجاوز النهاية أو-1)، فأعِد ما إذا كانت قيمةremainingهي0. - وإلا فأعِد
trueإذا أعاد استدعاءwalkعلى ابن أيسر موجود أو ابن أيمن موجود القيمةtrue. - أعِد
walk(0, targetSum).
def hasPathSum(tree, targetSum):
n = len(tree)
def walk(i, remaining):
# remaining is what the path still needs once it reaches node i.
remaining -= tree[i]
left, right = 2 * i + 1, 2 * i + 2
has_left = left < n and tree[left] != -1
has_right = right < n and tree[right] != -1
if not has_left and not has_right:
return remaining == 0 # a leaf: the path ends here
return (has_left and walk(left, remaining)) or (has_right and walk(right, remaining))
return walk(0, targetSum)البحث بالعمق باستخدام مكدس صريح
الفكرة
يحتفظ الاستدعاء التكراري برقم واحد في كل استدعاء. احتفظ به بنفسك بدلًا من ذلك، على مكدّس بجوار كل عقدة: مجموع المسار من الجذر نزولًا إلى تلك العقدة. ابدأ بـ (0, tree[0])؛ تحصل كل عقدة فرعية على مجموع أبيها مضافًا إليه قيمتها. أخرج زوجًا من المكدّس. إذا كانت العقدة ورقة ومجموعها targetSum، فقد انتهيت؛ وإلا فأضف أبناءها الفعليين إلى المكدّس.
تُضاف كل عقدة فعلية إلى المكدّس مرة واحدة، بزمن O(n)، ويحتوي المكدّس على أخ واحد منتظر تقريبًا لكل مستوى، أي O(h). تعمل الحلقة نفسها على شجرة عميقة قائمة على المؤشرات، حيث قد ينفد المكدّس من الاستدعاء التكراري.
الخوارزمية
- ادفع
(0, tree[0])إلى المكدس. - أخرج زوجًا
(i, total)وانظر إلى موضعي الابنين2*i+1و2*i+2. - إذا لم يكن أيٌّ من الابنين حقيقيًا وكان
totalيساويtargetSum، فأعِدtrue. - ادفع كل ابن حقيقي
cبالشكل(c, total + tree[c]). - عندما يصبح المكدس فارغًا، أعِد
false.
def hasPathSum(tree, targetSum):
n = len(tree)
stack = [(0, tree[0])] # (node index, sum of the path from the root to it)
while stack:
i, total = stack.pop()
left, right = 2 * i + 1, 2 * i + 2
has_left = left < n and tree[left] != -1
has_right = right < n and tree[right] != -1
if not has_left and not has_right and total == targetSum:
return True # a leaf whose path adds up
if has_left:
stack.append((left, total + tree[left]))
if has_right:
stack.append((right, total + tree[right]))
return False
أخطاء شائعة وحالات حدّية
- مقارنة المجموع عند كل عقدة. في المثال الثاني،
3 + 9 = 12تتحقق عند9، ولها ابن، لذا تكون الإجابةfalse. - اعتبار الموضع الفارغ لابن نهايةً للمسار. عندئذٍ تنهي
9في المثال الثاني مسارًا عبر موضع ابنها الأيسر الفارغ. - نسيان الجذر وحده. العقدة الواحدة ورقة، لذا فإن
[4]مع الهدف4نتيجتهtrue، وكذلك[0]مع الهدف0. - القراءة بعد نهاية المصفوفة. قد تكون فهارس أبناء الورقة القريبة من النهاية أكبر من فهرس آخر عنصر.
- الخلط بين الإزاحة في Lua وR. أبقِ فهارس العقد تبدأ من 0 عند استخدام
2*i+1، واقرأtree[i + 1].
أسئلة شائعة3
ما هو التعقيد الزمني لمسألة مجموع المسار؟
O(n): تتم زيارة كل عقدة مرة واحدة على الأكثر، ويمكن أن يتوقف البحث عند أول ورقة تطابق. المساحة الإضافية هي O(h) للمسار الجاري استكشافه، سواء أكانت إطارات استدعاء أم عناصر في مكدسك الخاص.
هل يمكن حل مسألة مجموع المسار باستخدام البحث بالعرض أولًا (BFS)؟
نعم. ضع أزواجًا تتكوّن من عقدة ومجموع المسار الخاص بها في قائمة انتظار بدلًا من المكدس، وتحقّق من كل ورقة عند إخراجها. يظل الزمن O(n)، لكن قائمة الانتظار قد تحتوي على مستوى كامل، أي نحو نصف عقد الشجرة الكاملة، بينما يحتوي المكدس على عقدة واحدة تقريبًا لكل مستوى.
كيف تجد كل مسار يساوي مجموعُه الهدف؟
احتفظ بقائمة العقد على المسار الحالي أثناء نزولك، وانسخها إلى الإجابة عند كل ورقة يطابق مجموعها المطلوب، وأزل العقدة الأخيرة عند الرجوع إلى الأعلى. يظل الاجتياز كما هو. قد يكون نسخ المسارات أكثر كلفة من الاجتياز عندما تتطابق أوراق كثيرة.
مسائل مشابهة
مسائل تعتمد على الأفكار نفسها. حلّ اثنتين أو ثلاث منها يثبّت النمط.
def hasPathSum(tree, targetSum):
# اكتب الكود هنااختبار #1
اختبار #2
اختبار #3
الوسائط
tree = [3, 9, 6, -1, 2, 1, 7] targetSum = 14
الإخراج المتوقع
true