المسارات الفريدة وأقل مجموع مسار في الشبكات
املأ جدول DP ثنائي الأبعاد للمسارات الفريدة مع العوائق وبدونها، ثم عدّله لتقليل مجموع القيم على طول المسار
المسارات الفريدة وأقل مجموع مسار في الشبكات درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
المسارات الفريدة على شبكة
تسأل مسألة المسارات الفريدة (LeetCode 62): في شبكة m×n، كم عدد المسارات المختلفة من الزاوية العلوية اليسرى إلى الزاوية السفلية اليمنى إذا كان مسموحًا بالحركة إلى اليمين أو إلى الأسفل فقط؟ في شبكة بحجم 3×7، تكون الإجابة 28. والفكرة الأساسية هي أن كل مسار يصل إلى الخلية (i,j) لا بد أن يأتي إما من (i-1,j) (أعلى الخلية) أو من (i,j-1) (يسارها)، مما يعطي صياغة طبيعية لـ DP ثنائي الأبعاد.
# 3x7 grid: robot starts at (0,0), goes to (2,6)
# Must make exactly 2 down-moves and 6 right-moves
# Total moves = 8, choose 2 for down = C(8,2) = 28
import math
print('Unique paths 3x7:', math.comb(3+7-2, 3-1)) # 28
print('Unique paths 3x3:', math.comb(3+3-2, 3-1)) # 6
print('Unique paths 2x2:', math.comb(2+2-2, 2-1)) # 2جدول DP ثنائي الأبعاد للمسارات الفريدة
عرّف dp[i][j] بأنه عدد المسارات التي تصل إلى الخلية (i,j). تكون جميع قيم الصف الأول والعمود الأول مساوية لـ 1، إذ لا توجد إلا طريقة واحدة للوصول إلى أي خلية في الصف العلوي أو العمود الأيسر. أما الخلايا الأخرى فتعتمد على العلاقة: dp[i][j] = dp[i-1][j] + dp[i][j-1]. املأ الجدول صفًا بعد صف، وتكون الإجابة dp[m-1][n-1]. التعقيد الزمني هو O(m×n)، وتعقيد المساحة هو O(m×n)، ويمكن اختزاله إلى O(n).
def unique_paths(m, n):
dp = [[1] * n for _ in range(m)]
# First row and column stay as 1s (base cases)
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
print(unique_paths(3, 7)) # 28
print(unique_paths(3, 3)) # 6
print(unique_paths(1, 1)) # 1 (already at destination)تحسين المساحة إلى O(n)
بما أن dp[i][j] يعتمد فقط على الصف الحالي والصف السابق، يمكنك استبدال الجدول الكامل ثنائي الأبعاد بمصفوفة أحادية البعد. هيّئ جميع القيم إلى 1، ثم حدّثها في مكانها لكل صف باستخدام: dp[j] += dp[j-1]. بعد معالجة الصف i، تحتوي dp[j] على القيمة التي كانت تمثّل dp[i][j] في الجدول ثنائي الأبعاد. وهذا نمط شائع لتحسين مسائل DP ثنائية الأبعاد.
def unique_paths_1d(m, n):
dp = [1] * n # initial row: all 1s
for i in range(1, m):
for j in range(1, n):
dp[j] += dp[j-1] # dp[j] was dp[i-1][j], dp[j-1] is dp[i][j-1]
return dp[n-1]
print(unique_paths_1d(3, 7)) # 28
print(unique_paths_1d(3, 3)) # 6
# Or use math for O(1)
import math
print(math.comb(3+7-2, 3-1)) # 28المسارات الفريدة II: العوائق
تضيف مسألة المسارات الفريدة II (LeetCode 63) عوائق (خلايا تحمل القيمة 1) إلى الشبكة. ويكون أي مسار يمر عبر عائق غير صالح، لذا تكون dp[i][j] = 0 إذا كانت obstacle[i][j] == 1. وإلا فتبقى علاقة التكرار كما هي: dp[i][j] = dp[i-1][j] + dp[i][j-1]. وإذا كانت نقطة البداية أو النهاية محجوبة، تكون الإجابة 0 مباشرةً. هيّئ الحالات الأساسية بعناية؛ فبمجرد ظهور 1 في الصف الأول أو العمود الأول، تصبح جميع الخلايا اللاحقة في ذلك الصف أو العمود مساوية لـ 0.
def unique_paths_with_obstacles(obstacle_grid):
m, n = len(obstacle_grid), len(obstacle_grid[0])
dp = [[0] * n for _ in range(m)]
# First row
for j in range(n):
if obstacle_grid[0][j] == 1: break
dp[0][j] = 1
# First column
for i in range(m):
if obstacle_grid[i][0] == 1: break
dp[i][0] = 1
for i in range(1, m):
for j in range(1, n):
if obstacle_grid[i][j] == 0:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
grid = [[0,0,0],[0,1,0],[0,0,0]]
print(unique_paths_with_obstacles(grid)) # 2مسألة مجموع المسار الأدنى
تسأل مسألة مجموع المسار الأدنى (LeetCode 64): إذا كانت لديك شبكة m×n مملوءة بأعداد غير سالبة، فما المسار من الزاوية العلوية اليسرى إلى الزاوية السفلية اليمنى الذي يقلّل مجموع جميع الأعداد على المسار، مع السماح بالحركة إلى اليمين أو إلى الأسفل فقط؟ على سبيل المثال، في [[1,3,1],[1,5,1],[4,2,1]] يعطي المسار 1→3→1→1→1 المجموع 7. وتكون حالة DP مماثلة لحالة المسارات الفريدة، لكن علاقة التكرار تستخدم القيمة الدنيا بدلًا من الجمع.
grid = [[1, 3, 1],
[1, 5, 1],
[4, 2, 1]]
# Optimal path: (0,0)→(0,1)→(0,2)→(1,2)→(2,2)
# Values: 1 + 3 + 1 + 1 + 1 = 7
print('Expected minimum path sum:', 7)تنفيذ DP لمجموع المسار الأدنى
عرّف dp[i][j] بأنه أقل تكلفة للوصول إلى الخلية (i,j). الحالة الأساسية: dp[0][0] = grid[0][0]. الصف الأول: dp[0][j] = dp[0][j-1] + grid[0][j] (الطريقة الوحيدة للوصول هي من اليسار). العمود الأول: dp[i][0] = dp[i-1][0] + grid[i][0] (الطريقة الوحيدة للوصول هي من الأعلى). الحالة العامة: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]). وهذا تطبيق مباشر لمبدأ الأمثلية.
def min_path_sum(grid):
m, n = len(grid), len(grid[0])
dp = [[0]*n for _ in range(m)]
dp[0][0] = grid[0][0]
for j in range(1, n): # first row
dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m): # first column
dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
return dp[m-1][n-1]
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum(grid)) # 7مجموع المسار الأدنى في مكانه
إذا كان مسموحًا بتعديل شبكة الإدخال، يمكنك تحديثها في مكانها لتجنّب تخصيص جدول DP منفصل. ويقلّل ذلك المساحة الإضافية إلى O(1)، باستثناء مساحة الإدخال. قد يسألك المحاورون أحيانًا عن هذا التحسين، لذا تحقّق من السماح بتعديل الإدخال قبل تنفيذه. وإذا لم يكن ذلك مسموحًا، فإن حيلة المصفوفة المتدحرجة أحادية البعد توفّر مساحة O(n) دون تعديل الإدخال.
def min_path_sum_inplace(grid):
m, n = len(grid), len(grid[0])
# Mutate in place
for i in range(m):
for j in range(n):
if i == 0 and j == 0: continue
if i == 0:
grid[i][j] += grid[i][j-1]
elif j == 0:
grid[i][j] += grid[i-1][j]
else:
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
return grid[m-1][n-1]
import copy
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_inplace(copy.deepcopy(grid))) # 7مجموع المسار الأدنى في مثلث
تطلب مسألة المثلث (LeetCode 120) حساب أقل مجموع لمسار من أعلى المثلث إلى أسفله في مصفوفة مثلثية، حيث تنتقل كل خطوة إلى عدد مجاور في الصف التالي. ويُعد DP من الأسفل إلى الأعلى أبسط حل: ابدأ من الصف قبل الأخير، ولكل خلية أضف أصغر قيمة من الخليتين الموجودتين أسفلها مباشرةً. وتجنّب هذه الطريقة تتبّع فهارس البداية، كما ترفع الإجابة طبيعيًا إلى قمة المثلث.
def minimum_total(triangle):
# Bottom-up: start from second-to-last row
dp = triangle[-1][:] # copy of bottom row
for row in range(len(triangle) - 2, -1, -1):
for col in range(len(triangle[row])):
dp[col] = triangle[row][col] + min(dp[col], dp[col+1])
return dp[0]
triangle = [
[2],
[3, 4],
[6, 5, 7],
[4, 1, 8, 3]
]
print(minimum_total(triangle)) # 11 (2+3+5+1)البرمجة الديناميكية على شبكة الزنزانة
تطلب مسألة لعبة الزنزانة (LeetCode 174) حساب أقل صحة ابتدائية لازمة لإنقاذ أميرة في الزاوية السفلية اليمنى لشبكة تحتوي على خلايا سالبة (ضرر) وموجبة (شفاء). يجب التحرك إلى اليمين أو إلى الأسفل. والحيلة هي ملء جدول DP بالعكس (من الزاوية السفلية اليمنى إلى الزاوية العلوية اليسرى)، وحساب الحد الأدنى للصحة اللازمة في كل خلية. في كل خلية: dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j]). ويجب أن تبقى الصحة دائمًا 1 على الأقل.
def calculate_minimum_hp(dungeon):
m, n = len(dungeon), len(dungeon[0])
dp = [[0]*n for _ in range(m)]
# Fill from bottom-right
dp[m-1][n-1] = max(1, 1 - dungeon[m-1][n-1])
for i in range(m-2, -1, -1): # last column
dp[i][n-1] = max(1, dp[i+1][n-1] - dungeon[i][n-1])
for j in range(n-2, -1, -1): # last row
dp[m-1][j] = max(1, dp[m-1][j+1] - dungeon[m-1][j])
for i in range(m-2, -1, -1):
for j in range(n-2, -1, -1):
need = min(dp[i+1][j], dp[i][j+1])
dp[i][j] = max(1, need - dungeon[i][j])
return dp[0][0]
dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]]
print(calculate_minimum_hp(dungeon)) # 7مقارنة مسائل DP على الشبكات
تشترك مسائل DP على الشبكات في البنية نفسها، لكنها تختلف في اتجاه الملء وعملية الانتقال: تستخدم المسارات الفريدة الجمع (لعدّ جميع الطرق). ويستخدم مجموع المسار الأدنى القيمة الدنيا (لتحسين المسار). أما لعبة الزنزانة فتمتلئ بالعكس (لحساب الصحة اللازمة انطلاقًا من المستقبل). عند مواجهة مسألة DP جديدة على شبكة، اسأل نفسك: (1) ماذا تمثّل كل خلية؟ (2) في أي اتجاه أملأ الجدول؟ (3) ما العملية التي تدمج الجيران؟ تكشف الإجابة عن هذه الأسئلة الثلاثة الحل الكامل.
# Summary: Grid DP Patterns
#
# Problem Fill Dir Transition
# Unique Paths top-left dp[i][j] = dp[i-1][j] + dp[i][j-1]
# Unique Paths II top-left same but 0 if obstacle
# Min Path Sum top-left dp[i][j] = grid[i][j] + min(above, left)
# Triangle bottom-up dp[col] = row[col] + min(dp[col], dp[col+1])
# Dungeon bottom-right max(1, min(right, down) - cell)
# Recognise the pattern, write the transition, verify with examples
print('Grid DP summary complete')ملخص التعقيد لمسائل DP على الشبكات
تعمل جميع مسائل DP على الشبكات هنا في زمن O(m×n). وتتراوح المساحة من O(m×n) عند استخدام جدول كامل، إلى O(n) باستخدام مصفوفة متدحرجة أحادية البعد، وإلى O(1) كمساحة إضافية عندما يمكن تعديل الشبكة في مكانها. في المقابلات، اذكر تحسين المساحة إلى O(n) بعد عرض حل O(m×n)، فهذا يبيّن إدراكك للمفاضلات. وفكّر أيضًا في جميع المسائل فيما إذا كان هناك اختصار جشع ممكن، مثل الصيغة الرياضية للمسارات الفريدة.
# O(n) space version of Min Path Sum
def min_path_sum_1d(grid):
m, n = len(grid), len(grid[0])
dp = [float('inf')] * n
dp[0] = 0
for i in range(m):
dp[0] += grid[i][0] # first column: only from above
for j in range(1, n):
dp[j] = grid[i][j] + min(dp[j], dp[j-1])
return dp[n-1]
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_1d(grid)) # 7اختبار سريع
اختبر فهمك لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
في هذا الدرس تعلّمت أن: المسارات الفريدة تملأ جدولًا ثنائي الأبعاد باستخدام dp[i][j] = dp[i-1][j] + dp[i][j-1]، ويمكن حسابها في O(1) باستخدام التوافقيات، وأن مجموع المسار الأدنى يستخدم البنية نفسها، لكنه يستبدل الجمع بالحد الأدنى لحساب تكلفة المسار المثلى، وأن جميع مسائل DP على الشبكات تشترك في نمط تعريف حالة لكل خلية واختيار عامل انتقال (الجمع أو الحد الأدنى أو الحد الأقصى). بعد ذلك سنستكشف أطول متتالية جزئية مشتركة باستخدام DP ثنائي الأبعاد على تسلسلين.
الأسئلة الشائعة
هل درس «المسارات الفريدة وأقل مجموع مسار في الشبكات» مجاني؟
نعم — نص درس «المسارات الفريدة وأقل مجموع مسار في الشبكات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «المسارات الفريدة وأقل مجموع مسار في الشبكات»؟
املأ جدول DP ثنائي الأبعاد للمسارات الفريدة مع العوائق وبدونها، ثم عدّله لتقليل مجموع القيم على طول المسار تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟
لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «المسارات الفريدة وأقل مجموع مسار في الشبكات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟
نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- المسارات الفريدة وأقل مجموع مسار في الشبكات
- أطول تتابع مشترك
- مسافة التحرير (Levenshtein)
- تحسين المساحة في DP ثنائي الأبعاد