شرح مسائل صعبة: Word Ladder II وAlien Dictionary
تصدَّ لمسألتين صعبتين من البداية إلى النهاية — word-ladder-II باستخدام BFS والتراجع، وalien-dictionary باستخدام الترتيب الطوبولوجي — مع شرح كامل.
شرح مسائل صعبة: Word Ladder II وAlien Dictionary درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
لماذا تختلف المسائل الصعبة
تختلف مسائل LeetCode الصعبة عن المسائل المتوسطة بطريقتين أساسيتين: (1) تتطلب الجمع بين تقنيتين خوارزميتين أو أكثر، و(2) غالبًا لا يكون الحل الأمثل واضحًا من نص المسألة وحده — إذ يجب أن تتجاوز الوصف الظاهري لتكتشف بنية الرسم البياني أو البرمجة الديناميكية الكامنة وراءه. تُعد Word Ladder II وAlien Dictionary من المسائل الصعبة النموذجية التي تتكرر في مقابلات FAANG.
النهج المناسب للمسائل الصعبة هو: لا تحاولوا رؤية الحل الكامل منذ البداية. بدلًا من ذلك، قسّموه إلى مسائل فرعية، وحددوا بنية كل مسألة فرعية، وحلوا كل واحدة منها على حدة، ثم اربطوا بينها. هذا التفكير المعياري هو مفتاح حل المسائل الصعبة تحت الضغط.
# Hard problem meta-strategy
strategy = [
'1. Read the problem 2x — hard problems often have subtle constraints',
'2. Model it as a known structure: graph? DP table? sorted order?',
'3. Break into sub-problems: separate the graph-building from the traversal',
'4. Solve sub-problems in order, verifying each before connecting',
'5. Handle the edge case where no solution exists (empty result, -1, [])',
'6. Optimise only after the correct but slow solution works',
]
print('Hard problem meta-strategy:')
for step in strategy:
print(f' {step}')Word Ladder II: نص المسألة
Word Ladder II (LeetCode 126): بالنظر إلى كلمة بداية، وكلمة نهاية، وقائمة كلمات، أوجدوا جميع سلاسل التحويل الأقصر من البداية إلى النهاية. تغيّر كل خطوة حرفًا واحدًا بالضبط، ويجب أن تكون كل كلمة وسيطة موجودة في قائمة الكلمات. هذه المسألة أصعب بكثير من Word Ladder I (التي تبحث عن مسار أقصر واحد فقط)، لأن عليكم تعداد جميع المسارات المثلى.
مثال: beginWord='hit'، endWord='cog'، wordList=['hot','dot','dog','lot','log','cog'] → [['hit','hot','dot','dog','cog'],['hit','hot','lot','log','cog']]. طول كل منهما 5.
# Word Ladder II problem breakdown
begin_word = 'hit'
end_word = 'cog'
word_list = ['hot','dot','dog','lot','log','cog']
# What we need:
# 1. Build a graph: word -> set of words that differ by one character
# 2. BFS to find the MINIMUM number of steps (shortest path distance)
# 3. DFS/backtracking to enumerate ALL paths of that minimum length
# Key insight: BFS finds shortest distance; DFS reconstructs all shortest paths
# Two-phase approach:
print('Phase 1: BFS from begin_word to find min distance to each word')
print('Phase 2: DFS/backtrack from end_word using only edges that decrease distance')
print()
print(f'Input: {begin_word} -> {end_word}')
print(f'Word list: {word_list}')
print('Expected: [[hit,hot,dot,dog,cog],[hit,hot,lot,log,cog]]')Word Ladder II: مرحلة BFS
في المرحلة الأولى، نفّذوا BFS مستوىً تلو الآخر انطلاقًا من كلمة البداية. في كل مستوى، نجد جميع الجيران (الكلمات التي تختلف بحرف واحد). ونسجل المستوى (المسافة من البداية) الذي نصل فيه إلى كل كلمة للمرة الأولى. لا نتوقف عند الوصول إلى كلمة النهاية — بل نتابع حتى نهاية المستوى الذي عُثر فيه على end_word، لضمان استكشاف جميع أقصر المسارات.
والأهم أننا نبني قاموس parents يربط كل كلمة بمجموعة الكلمات التي يمكن أن تسبقها في أي أقصر مسار. وهذا هو الرسم البياني الذي نستخدمه في المرحلة الثانية للتراجع.
from collections import defaultdict, deque
def find_parents(begin, end, word_set):
parents = defaultdict(set)
layer = {begin}
found = False
while layer and not found:
next_layer = set()
for word in layer:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
new_word = word[:i] + c + word[i+1:]
if new_word in word_set and new_word not in parents:
next_layer.add(new_word)
parents[new_word].add(word)
if new_word == end:
found = True
layer = next_layer
return parents if found else {}
words = {'hot','dot','dog','lot','log','cog'}
parents = find_parents('hit', 'cog', words)
print('Parents map (which words can precede each word):')
for word, preds in sorted(parents.items()):
print(f' {word}: {preds}')Word Ladder II: مرحلة التراجع باستخدام DFS
في المرحلة الثانية، استخدموا التراجع باستخدام DFS انطلاقًا من كلمة النهاية، مع اتباع خريطة parents بالعكس. نبني المسارات من النهاية إلى البداية (ثم نعكسها). عند الوصول إلى كلمة البداية، نكون قد وجدنا أقصر مسار كامل. وتضمن خريطة الآباء أن جميع المسارات التي نعثر عليها ذات طول أدنى — فلا يمكننا «الانحراف» إلى مسار أطول.
هذا النهج المؤلف من مرحلتين (BFS للمستويات، وDFS لإعادة بناء المسارات) هو الحل القياسي، ويعمل بتعقيد O(n × L × 26) في BFS، حيث n = حجم قائمة الكلمات وL = طول الكلمة، إضافةً إلى O(K × L) في DFS، حيث K = عدد أقصر المسارات.
def find_ladders(beginWord, endWord, wordList):
word_set = set(wordList)
if endWord not in word_set:
return []
# Phase 1: BFS to build parents map
parents = defaultdict(set)
layer = {beginWord}
found = False
visited = {beginWord}
while layer and not found:
next_layer = set()
for word in layer:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
nw = word[:i] + c + word[i+1:]
if nw in word_set and nw not in visited:
next_layer.add(nw)
parents[nw].add(word)
if nw == endWord: found = True
visited |= next_layer
layer = next_layer
# Phase 2: DFS backtrack from endWord to beginWord
result = []
def dfs(word, path):
if word == beginWord:
result.append(path[::-1])
return
for parent in parents[word]:
dfs(parent, path + [parent])
dfs(endWord, [endWord])
return result
print(find_ladders('hit','cog',['hot','dot','dog','lot','log','cog']))Alien Dictionary: نص المسألة
Alien Dictionary (LeetCode 269): بالنظر إلى قائمة كلمات مرتبة معجميًا في لغة فضائية، حددوا ترتيب الأحرف في تلك اللغة. أعيدوا ترتيب الأحرف في صورة سلسلة نصية. وإذا لم يوجد ترتيب صالح (بسبب التناقض)، فأعيدوا سلسلة فارغة.
مثال: ['wrt','wrf','er','ett','rftt'] → 'wertf'. من خلال مقارنة الكلمات المتجاورة: ‘t’ < ‘f’ (من wrt مقابل wrf)، و‘w’ < ‘e’ (من wrt مقابل er)، و‘r’ < ‘t’ (من er مقابل ett)، و‘e’ < ‘r’ (من ett مقابل rftt). هذا ترتيب طوبولوجي لقيود ترتيب هذه الأحرف.
words = ['wrt', 'wrf', 'er', 'ett', 'rftt']
# Compare adjacent pairs to extract ordering:
# wrt vs wrf: first diff at index 2: t < f (t comes before f)
# wrf vs er: first diff at index 0: w < e (w comes before e)
# er vs ett: first diff at index 1: r < t (r comes before t)
# ett vs rftt:first diff at index 0: e < r (e comes before r)
ordering_constraints = [
('t', 'f', 'from wrt vs wrf'),
('w', 'e', 'from wrf vs er'),
('r', 't', 'from er vs ett'),
('e', 'r', 'from ett vs rftt'),
]
print('Ordering constraints extracted from adjacent word pairs:')
for a, b, source in ordering_constraints:
print(f' {a} -> {b} ({source})')
print('\nThis is a directed graph: find topological order = alien alphabet order')Alien Dictionary: بناء الرسم البياني
الخطوة الأولى هي استخراج القيود: قارنوا كل زوج متجاور من الكلمات، واعثروا على أول حرف مختلف، ثم أضيفوا حافة موجهة من الحرف الأصغر إلى الحرف الأكبر. إذا كانت كلمة بادئة للكلمة التالية لكنها أطول منها (مثل ‘abc’ قبل ‘ab’)، فالإدخال غير صالح — أعيدوا سلسلة فارغة فورًا.
جميع الأحرف الظاهرة في قائمة الكلمات هي عقد في الرسم البياني، حتى إن لم تكن لها أي قيود ترتيب. ويمكن أن تظهر هذه العقد المعزولة في أي موضع من الترتيب النهائي.
from collections import defaultdict
def build_alien_graph(words):
adj = defaultdict(set) # char -> set of chars that come after it
in_degree = {c: 0 for word in words for c in word}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i+1]
min_len = min(len(w1), len(w2))
found_diff = False
for j in range(min_len):
if w1[j] != w2[j]:
if w2[j] not in adj[w1[j]]: # avoid duplicate edges
adj[w1[j]].add(w2[j])
in_degree[w2[j]] += 1
found_diff = True
break
if not found_diff and len(w1) > len(w2):
return {}, {} # invalid: 'abc' before 'ab'
return adj, in_degree
words = ['wrt', 'wrf', 'er', 'ett', 'rftt']
adj, in_degree = build_alien_graph(words)
print('Adjacency list (directed):', {k: list(v) for k, v in adj.items()})
print('In-degrees:', in_degree)Alien Dictionary: الترتيب الطوبولوجي
بعد بناء الرسم البياني، طبّقوا الترتيب الطوبولوجي باستخدام BFS وفق خوارزمية Kahn: املؤوا طابورًا بجميع الأحرف ذات الدرجة الداخلة 0 (أي التي لا تملك متطلبات سابقة). عالجوا كل حرف، وأنقصوا الدرجة الداخلة للعقد اللاحقة له. وعندما تصل الدرجة الداخلة لعقدة لاحقة إلى 0، أضيفوها إلى الطابور. اجمعوا الأحرف حسب ترتيب معالجتها — فهذا هو الترتيب الأبجدي الفضائي.
إذا احتوت النتيجة على جميع الأحرف، فلدينا ترتيب صالح. أما إذا كان عدد الأحرف أقل من المتوقع، فهناك دورة — أي إن القيود متناقضة، ونعيد سلسلة فارغة.
from collections import deque, defaultdict
def alien_order(words):
adj = defaultdict(set)
in_degree = {c: 0 for word in words for c in word}
for i in range(len(words) - 1):
w1, w2 = words[i], words[i + 1]
min_len = min(len(w1), len(w2))
found = False
for j in range(min_len):
if w1[j] != w2[j]:
if w2[j] not in adj[w1[j]]:
adj[w1[j]].add(w2[j])
in_degree[w2[j]] += 1
found = True; break
if not found and len(w1) > len(w2):
return '' # invalid: 'abc' before 'ab'
# Kahn's BFS topological sort
queue = deque([c for c in in_degree if in_degree[c] == 0])
result = []
while queue:
c = queue.popleft()
result.append(c)
for neighbor in sorted(adj[c]): # sort for determinism
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return ''.join(result) if len(result) == len(in_degree) else ''
print(alien_order(['wrt','wrf','er','ett','rftt'])) # e.g., 'wertf'
print(alien_order(['z','x'])) # 'zx'
print(alien_order(['z','x','z'])) # '' (cycle z->x->z)التعامل مع الحالات الطرفية: كلتا المسألتين
تحتوي كل من Word Ladder II وAlien Dictionary على حالات طرفية دقيقة قد تؤدي إلى إجابات خاطئة إن لم تُعالَج:
- Word Ladder II: beginWord وendWord متطابقتان (أعيدوا
[[beginWord]]أو طولًا يساوي 1). endWord غير موجودة في wordList (أعيدوا قيمة فارغة). لا يوجد مسار (أعيدوا قيمة فارغة). - Alien Dictionary: كلمات مكررة (لا تستخرجوا أي قيد). كلمة واحدة (أعيدوا جميع الأحرف الفريدة). دورة في القيود (أعيدوا ''). كلمة أطول من الكلمة التالية التي تشكل بادئة لها (إدخال غير صالح، أعيدوا ''). جميع الأحرف معزولة (أعيدوا أي ترتيب).
# Edge case tests for Word Ladder II
def test_word_ladder_edge_cases():
from collections import defaultdict
def find_ladders(begin, end, word_list):
# [abbreviated implementation for testing]
if end not in word_list: return []
if begin == end: return [[begin]]
return [] # placeholder
tests = [
('hit', 'cog', ['hot','dot','dog','lot','log'], []), # no path (cog missing)
('hit', 'hit', ['hit'], [['hit']]), # begin==end
('a', 'c', ['a','b','c'], [['a','c']]), # short words
]
for begin, end, wl, expected in tests:
result = find_ladders(begin, end, wl)
print(f'{begin}->{end}: result={result}')
# Edge case tests for Alien Dictionary
def test_alien_edge_cases():
from collections import defaultdict, deque
# (using alien_order from previous scene)
tests = [
(['abc', 'ab'], ''), # 'abc' before 'ab' = invalid
(['a'], 'a'), # single word
(['z','z'], 'z'), # duplicate: no constraint
]
print('Alien dictionary edge cases:')
for words, expected in tests:
print(f' {words} -> expected: "{expected}"')
test_word_ladder_edge_cases()
test_alien_edge_cases()تحليل التعقيد: كلتا المسألتين
تعقيد Word Ladder II: تعمل مرحلة BFS بتعقيد O(n × L × 26)، حيث n = عدد الكلمات في القائمة وL = طول الكلمة. لكل كلمة في كل مستوى من مستويات BFS، نولّد 26L كلمة مرشحة ونتحقق من وجودها في مجموعة الكلمات (بتكلفة O(1) لكل تحقق). وتعمل مرحلة DFS بتعقيد O(K × L)، حيث K = عدد أقصر المسارات (وقد يكون أُسّيًا من الناحية النظرية).
تعقيد Alien Dictionary: يستغرق بناء الرسم البياني O(C)، حيث C = إجمالي عدد الأحرف في جميع الكلمات. ويستغرق الترتيب الطوبولوجي O(V + E)، حيث V = عدد الأحرف الفريدة وE = عدد قيود الترتيب. ويكون التعقيد الإجمالي O(C)، أي O(إجمالي عدد الأحرف في الإدخال).
# Complexity analysis for both problems
complexities = [
{
'problem': 'Word Ladder II',
'time': 'O(n * L * 26) BFS + O(K * L) DFS backtracking',
'space': 'O(n * L) for word set + parents map',
'notes': 'K (number of shortest paths) can be exponential in pathological cases',
},
{
'problem': 'Alien Dictionary',
'time': 'O(C) where C = total characters in all words',
'space': 'O(V + E) for adjacency list',
'notes': 'V <= 26 (alphabet), E <= V^2 = 676; often treated as O(C) total',
},
]
for c in complexities:
print(f'{c["problem"]}:')
print(f' Time: {c["time"]}')
print(f' Space: {c["space"]}')
print(f' Notes: {c["notes"]}')
print()ملخص النمط: قالبان قابلان لإعادة الاستخدام
تعلّمكم كلتا المسألتين أنماطًا قابلة لإعادة الاستخدام. Word Ladder II = BFS لحساب المسافات + DFS لإعادة بناء المسارات: يظهر هذا النمط كلما احتجتم إلى جميع أقصر المسارات في رسم بياني غير موزون. ابنوا خريطة الآباء أثناء BFS، ثم نفّذوا التراجع من الوجهة إلى المصدر.
Alien Dictionary = استخراج الحواف + الترتيب الطوبولوجي: يظهر هذا النمط كلما أُعطيتم تسلسلًا مرتبًا وطُلب منكم استنتاج قواعد الترتيب الكامنة وراءه. استخرجوا القيود الموجهة من الأزواج المتجاورة، ثم طبّقوا خوارزمية Kahn. أعيدوا '' عند اكتشاف دورة (أي عند استحالة الترتيب).
# Pattern templates
print('Template 1: All Shortest Paths in Unweighted Graph')
template_1 = '''
1. BFS from source, recording parents[node] = set of nodes that lead to node
2. Continue each BFS level fully (do not stop at first endNode reach)
3. DFS backtrack from endNode, following parents map
4. Reverse each path found (built end->start, need start->end)
'''
print(template_1)
print('Template 2: Infer Ordering from Sorted Sequence')
template_2 = '''
1. Compare adjacent pairs, extract first differing element as directed constraint
2. Build adjacency list + in-degree map
3. Check for invalid input (prefix longer than successor)
4. Kahn's BFS topological sort
5. If result length < number of nodes => cycle => return invalid
'''
print(template_2)بناء الثقة في المسائل الصعبة
تبدو المسائل الصعبة مستحيلة في البداية، لكنها تصبح قابلة للتناول باستخدام النموذج الذهني المناسب. إليكم الأفكار الأساسية:
- افصلوا بين المسؤوليات: حلوا كل مسألة فرعية على حدة قبل ربطها بالمسائل الأخرى
- اعرفوا اللبنات الأساسية لديكم: BFS وDFS، والترتيب الطوبولوجي، وخوارزمية Dijkstra، وجداول DP — فالمسائل الصعبة تجمع بينها بطرق غير واضحة
- ابدؤوا بالأمثلة: تتبعوا المسألة يدويًا باستخدام مثال صغير لاكتشاف البنية الكامنة وراءها
- تحققوا من المسائل الفرعية: بعد تنفيذ المرحلة الأولى (بناء الرسم البياني)، اطبعوا الرسم البياني وتحققوا منه يدويًا قبل الانتقال إلى المرحلة الثانية
# Hard problem confidence-building practice plan
practice_plan = [
('Week 1', 'BFS/DFS fundamentals', ['Number of Islands', 'Clone Graph', 'Word Ladder I']),
('Week 2', 'Topological sort', ['Course Schedule I & II', 'Alien Dictionary (easy)']),
('Week 3', 'All-paths problems', ['All Paths to Target', 'Word Ladder II (hard)']),
('Week 4', 'Hard combos', ['Minimum Window Substring', 'Serialize/Deserialize Tree']),
]
print('4-week hard problem practice plan:')
for week, theme, problems in practice_plan:
print(f'\n{week} — {theme}:')
for p in problems:
print(f' - {p}')
print('\nAfter each problem, write:')
print(' 1. The pattern it belongs to')
print(' 2. The 2-3 key sub-problems')
print(' 3. One insight you would not have had before solving it')اختبار سريع
اختبروا فهمكم لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
في هذا الدرس تعلمتم أن Word Ladder II تستخدم BFS لبناء خريطة parents لجميع الأسلاف في أقصر المسارات، ثم تستخدم التراجع عبر DFS لتعداد جميع أقصر المسارات باتباع parents من النهاية إلى البداية، وأن Alien Dictionary تستخرج القيود الموجهة من أزواج الكلمات المتجاورة وتطبّق الترتيب الطوبولوجي وفق خوارزمية Kahn لترتيب الأحرف، وتعيد سلسلة فارغة عند اكتشاف دورة، وأن المسائل الصعبة تتجزأ إلى مسائل فرعية متعددة — مثل بناء الرسم البياني، والعثور على المسافات، وإعادة بناء المسارات — ويُحل كل منها على حدة باستخدام خوارزميات مألوفة. لقد أكملتم الآن دورة DSA Interview Prep كاملة. طبّقوا كل نمط وتقنية من هذا المسار في مقابلاتكم بثقة.
الأسئلة الشائعة
هل درس «شرح مسائل صعبة: Word Ladder II وAlien Dictionary» مجاني؟
نعم — نص درس «شرح مسائل صعبة: Word Ladder II وAlien Dictionary» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «شرح مسائل صعبة: Word Ladder II وAlien Dictionary»؟
تصدَّ لمسألتين صعبتين من البداية إلى النهاية — word-ladder-II باستخدام BFS والتراجع، وalien-dictionary باستخدام الترتيب الطوبولوجي — مع شرح كامل. تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟
لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «شرح مسائل صعبة: Word Ladder II وAlien Dictionary»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟
نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- ورقة غش للتعرّف على الأنماط
- مقابلة تجريبية محددة بوقت: مسائل سهلة ومتوسطة
- التعامل مع الحالات الحدّية والتواصل في المقابلة
- شرح مسائل صعبة: Word Ladder II وAlien Dictionary