Longest Increasing Path in a Matrix
لديك matrix، وهي شبكة من الأعداد الصحيحة تتكوّن من m صفوف وn أعمدة، معطاة على شكل قائمة من الصفوف. يتحرك المسار إلى الأعلى أو الأسفل أو اليسار أو اليمين (من دون أقطار أو التفاف حول الحواف)، ويجب أن تؤدي كل خطوة إلى قيمة أكبر تمامًا. أعد عدد الخلايا في أطول مسار من هذا النوع. تُعدّ الخلية الواحدة مسارًا يتكوّن من خلية واحدة.
الدالة
- matrixinteger-2d-array
- شبكة القيم، على هيئة قائمة من الصفوف متساوية الطول
- تُرجعinteger
- عدد الخلايا في أطول مسار متزايد تمامًا
القيود
1 ≤ m, n ≤ 100، حيثm = matrix.lengthوn = matrix[i].length- لكل صف الطول نفسه
n. 0 ≤ matrix[i][j] ≤ 231-1
أمثلة
- المدخلات
- matrix = [[9, 8, 3], [2, 7, 4], [1, 6, 5]]
- المخرجات
- 7
- الشرح
- يمتد المسار 3، 4، 5، 6، 7، 8، 9 نزولًا في العمود الأيمن، وعلى طول الصف السفلي ثم صعودًا في العمود الأوسط حتى 9: 7 خلايا. ومن أصغر قيمة، 1، تتكوّن أفضل المسارات من 5 خلايا.
- المدخلات
- matrix = [[2, 2, 2], [2, 5, 2]]
- المخرجات
- 2
- الشرح
- القيم المتساوية لا تمثّل صعودًا، لذا لا يسير أي مسار على طول قيم 2. أفضل مسار هو خطوة من إحدى قيم 2 الثلاث المحيطة بـ5 إلى 5: خليتان.
- المدخلات
- matrix = [[4, 4], [4, 4], [4, 4]]
- المخرجات
- 1
- الشرح
- كل قيمة هي 4، لذا لا يُسمح بأي خطوة في أي مكان. كل خلية بمفردها مسار طوله خلية واحدة، والإجابة هي 1.
سؤال إضافي
هل يمكنك أيضًا إرجاع خلايا أحد أطول المسارات، إلى جانب طوله؟
تلميحات
افتحها واحدًا تلو الآخر. كل تلميح يكشف أكثر قليلًا.
هل يمكن لمسارٍ ما أن يعود إلى خلية سبق أن زارها؟ راقب ما يحدث للقيم على طول الطريق.
القيم لا تزداد إلا، لذا لا يتكرر أيُّ مسار في خلية، ولا يعتمد أطول مسار يبدأ من خلية على كيفية وصولك إليها. وهو يساوي 1 زائد أطول مسار انطلاقًا من أفضل جار أكبر منها.
احسب هذا العدد مرة واحدة لكل خلية وخزّنه. إما أن تملأه باستخدام بحث عمق أول عبر الجيران ذوي القيم الأكبر، مع إدارة المكدس بنفسك، أو أزل طبقات الشبكة بدءًا من قممها، طبقة واحدة في كل مرة، وعدّ الطبقات.
الحل
ارسم سهمًا من كل خلية إلى كل جار أكبر منها. تزداد القيم على طول كل سهم، لذا لا يمكن لأي سلسلة من الأسهم أن تكوّن حلقة: الشبكة رسم بياني موجّه لا دوري، والإجابة هي أطول مسار فيه. في غياب الدورات، يعتمد أطول مسار من خلية على تلك الخلية فقط، لذا تحسبه مرة واحدة لكل خلية: O(m × n).
اتبع كل مسار متزايد
صحيحة، لكنها لا تنتهي في أكبر الاختبارات
الفكرة
ابدأ مسارًا من كل خلية، واتبع كل فرع إلى الجيران ذوي القيم الأكبر حتى لا يتبقى أيٌّ منها؛ أطول مسار هو الإجابة. تزداد القيم عند كل خطوة، لذا لا يعود المسار أبدًا إلى خلية، ولا يحتاج إلى مجموعة للخلايا التي تمت زيارتها.
يُعاد تنفيذ المسار من خلية في كل مرة يمر فيها مسار آخر عبرها. في شبكة بحجم 100 × 100، حيث تكون قيمة كل خلية هي مجموع صفها وعمودها، تؤدي كل خطوة إلى اليمين أو الأسفل إلى الارتفاع، ويغادر الزاوية العلوية اليسرى أكثر من 10^58 مسارًا.
الخوارزمية
- لكل خلية، أضف (تلك الخلية، 1) إلى مكدس.
- أخرج مدخلة (خلية، طول) وحدّث الإجابة باستخدام الطول.
- أضف (خلية مجاورة، طول + 1) لكل خلية مجاورة داخل الشبكة ذات قيمة أكبر بصرامة.
- كرّر حتى يصبح المكدس فارغًا، ثم انتقل إلى خلية البداية التالية.
- أعِد أكبر طول تمت رؤيته.
def longestIncreasingPath(matrix):
rows, cols = len(matrix), len(matrix[0])
dirs = ((1, 0), (-1, 0), (0, 1), (0, -1))
answer = 0
for sr in range(rows):
for sc in range(cols):
# Each entry is one path in progress: the cell it ends on and
# how many cells it has.
stack = [(sr, sc, 1)]
while stack:
r, c, length = stack.pop()
answer = max(answer, length)
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and matrix[nr][nc] > matrix[r][c]:
stack.append((nr, nc, length + 1))
return answerالبحث بالعمق مع التخزين المؤقت باستخدام مكدس خاص بك
الفكرة
لتكن best[cell] عدد الخلايا في أطول مسار متزايد يبدأ منها: 1 + max(best[nb]) على الجيران الأكبر nb، أو 1 إذا لم يوجد أيّ منها. تخزين هذه القيمة آمن لأن الخلايا التي تسبق cell في المسار كلها أصغر منها، ولا تأتي بعدها أبدًا. في المثال الأول، تكون قيمة 9 هي 1، وقيمة 8 هي 2، وقيمة 7 هي 3، وقيمة كل من 6 و2 هي 4، وقيمة كل من 5 و1 هي 5، وقيمة 4 هي 6، وقيمة 3 هي 7.
يمكن للمسار أن يمر عبر الخلايا العشرة آلاف كلها، وهذا أعمق مما يناسب الاستدعاء الذاتي، لذا استخدم مكدسًا خاصًا بك. تحاول الخلية الموجودة في القمة الاتجاه التالي، وتدفع جارًا أكبر لم يُنهَ بعد إلى المكدس. بعد تجربة الاتجاهات الأربعة كلها، أزلها من المكدس واضبط قيمة best لها. لا حاجة إلى علامة «قيد المعالجة»: فكل خلية في المكدس أكبر من الخلية التي تحتها.
الخوارزمية
- املأ
bestبالقيمة 0 (غير معروف بعد)، وعداد الاتجاهات بالقيمة 0 لكل خلية. - لكل خلية تكون قيمة
bestفيها 0، ادفعها إلى المكدس. - عند الخلية الموجودة في أعلى المكدس، زِد عدادها وادفع ذلك الجار إذا كان داخل الشبكة، وأكبر منها، ولم يُنهَ بعد.
- بعد فحص الاتجاهات الأربعة، أخرج الخلية من المكدس واجعل
bestيساوي 1 زائد أكبر قيمةbestبين جيرانها الأكبر منها، أو 1. - أعِد أكبر قيمة
best.
def longestIncreasingPath(matrix):
rows, cols = len(matrix), len(matrix[0])
dirs = ((1, 0), (-1, 0), (0, 1), (0, -1))
# best[r][c]: cells on the longest increasing path that starts at (r, c).
# 0 means not known yet.
best = [[0] * cols for _ in range(rows)]
# step[r][c]: how many of the 4 directions the search has tried from (r, c).
step = [[0] * cols for _ in range(rows)]
answer = 0
for sr in range(rows):
for sc in range(cols):
if best[sr][sc]:
continue
# Our own stack instead of recursion: a path can be thousands of
# cells long, past Python's limit of 1,000 nested calls.
stack = [(sr, sc)]
while stack:
r, c = stack[-1]
d = step[r][c]
if d < 4:
step[r][c] = d + 1
nr, nc = r + dirs[d][0], c + dirs[d][1]
# The stack only climbs, so a larger neighbour is never on it.
if 0 <= nr < rows and 0 <= nc < cols and matrix[nr][nc] > matrix[r][c] and best[nr][nc] == 0:
stack.append((nr, nc))
continue
# Every larger neighbour is finished: build on the best of them.
stack.pop()
length = 1
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and matrix[nr][nc] > matrix[r][c]:
length = max(length, best[nr][nc] + 1)
best[r][c] = length
answer = max(answer, length)
return answerأزل القمم من الشبكة
الفكرة
ابنِ الجدول نفسه من القيم الأعلى نزولًا، كما في الفرز الطوبولوجي لخوارزمية Kahn. تبدأ القمة، وهي خلية ليس لها جار أكبر، مسارًا من خلية واحدة: الطبقة 1. أزِل جميع القمم؛ والخلايا التي فقدت آخر جار أكبر لها تكون في الطبقة التالية. كرر ذلك حتى تصبح الشبكة فارغة. تُزال الخلية بعد جولة واحدة من إزالة آخر جار أكبر لها، لذا تساوي طبقتها best، ويكون عدد الطبقات هو الإجابة. في المثال الأول، الطبقات هي: 9؛ 8؛ 7؛ 2 و6؛ 1 و5؛ 4؛ 3.
احسب عدد الجيران الأكبر الذين ما زالوا موجودين لكل خلية. تؤدي إزالة خلية إلى إنقاص العدد لكل جار أصغر منها، والعدد 0 يضع الخلية في الطبقة التالية. تُزال كل خلية مرة واحدة: O(m × n)، من دون مكدس.
الخوارزمية
- لكل خلية، احسب عدد الجيران ذوي القيم الأكبر.
- ضع كل خلية يكون عددها 0 في الطبقة الحالية.
- ما دامت الطبقة غير فارغة، أضف 1 إلى عدد الطبقات. ولكل خلية فيها، قلّل عدد كل جار أصغر منها بصرامة، وضع الجار الذي يصل عدده إلى 0 في الطبقة التالية.
- اجعل الطبقة التالية هي الطبقة الحالية وكرّر.
- أعِد عدد الطبقات.
def longestIncreasingPath(matrix):
rows, cols = len(matrix), len(matrix[0])
dirs = ((1, 0), (-1, 0), (0, 1), (0, -1))
# higher[r][c]: neighbours of (r, c) with a larger value, not peeled yet.
higher = [[0] * cols for _ in range(rows)]
layer = []
for r in range(rows):
for c in range(cols):
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and matrix[nr][nc] > matrix[r][c]:
higher[r][c] += 1
# A peak: no neighbour is larger, so a path from it has one cell.
if higher[r][c] == 0:
layer.append((r, c))
layers = 0
while layer:
layers += 1
next_layer = []
for r, c in layer:
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and matrix[nr][nc] < matrix[r][c]:
higher[nr][nc] -= 1
# Its last larger neighbour is peeled: it is a peak now.
if higher[nr][nc] == 0:
next_layer.append((nr, nc))
layer = next_layer
# Each layer is one step further from a peak; the count is the longest path.
return layers
أخطاء شائعة وحالات حدّية
- المقارنة باستخدام
>=بدلًا من>. مع وجود رقمَي 4 متجاورين، تتنقل طريقة القوة الغاشمة ذهابًا وإيابًا إلى الأبد، وتقرأ عملية البحث باستخدام التخزين المؤقت طولًا لا يزال قيد الحساب. - استخدام الاستدعاء الذاتي في Python. القيم التي تتعرج عبر شبكة 100 × 100 تكوّن مسارًا من 10,000 خلية، أي عشرة أضعاف الحد الافتراضي البالغ 1,000 استدعاء.
- تجاوز الخلايا التي اكتمل حسابها كما تفعل عملية ملء الفيضان. طولها المخزّن هو بالضبط ما تحتاج إليه الخلية الحالية.
- البدء من أصغر قيمة فقط. في المثال الأول، تعطي القيمة 1 مسارًا من 5 خلايا، لكن الإجابة، 7، تبدأ من القيمة 3.
- بدء حساب الأطوال من 0. الإجابة لشبكة ذات قيم متساوية هي 1.
أسئلة شائعة3
ما هو التعقيد الزمني لإيجاد أطول مسار متزايد في مصفوفة؟
زمن ومساحة O(m × n)، باستخدام البحث بالعمق أولًا مع التخزين المؤقت أو بالتقشير: تُعالَج كل خلية مرة واحدة وتفحص جيرانها الأربعة. تجربة كل المسارات انطلاقًا من كل خلية تستغرق زمنًا أُسّيًا.
هل تُعدّ مسألة أطول مسار متزايد في مصفوفة مسألة برمجة ديناميكية أم مسألة في نظرية الرسوم البيانية؟
كلاهما. إنه أطول مسار في رسم بياني موجّه لا دوري، ويُحلّ باستخدام البرمجة الديناميكية وفق ترتيب طوبولوجي. ويعطي ترتيب الخلايا من أكبر قيمة إلى أصغرها ترتيبًا صالحًا آخر، بتكلفة O(m × n × log(m × n)) لعملية الفرز.
ما الفرق بين هذا وبين أطول تتابع متزايد؟
يمكن للمتتالية الجزئية أن تتخطى عناصر مع الحفاظ على ترتيبها؛ أما المسار هنا، فيجب أن ينتقل إلى خلية مجاورة، في أيٍّ من الاتجاهات الأربعة. وفي كليهما، لا يمكن لسلسلة متزايدة تمامًا أن تعود إلى نفسها في حلقة.
مسائل مشابهة
مسائل تعتمد على الأفكار نفسها. حلّ اثنتين أو ثلاث منها يثبّت النمط.
def longestIncreasingPath(matrix):
# اكتب الكود هنااختبار #1
اختبار #2
اختبار #3
وسيط
matrix = [[9, 8, 3], [2, 7, 4], [1, 6, 5]]
الإخراج المتوقع
7