0Pricing
Coding Interview Prep · 课时

困难问题 walkthrough:Word Ladder II 与 Alien Dictionary

端到端解决两个困难问题——使用 BFS + 回溯解决 Word Ladder II,使用拓扑排序解决 Alien Dictionary——并获得完整讲解。

困难问题 walkthrough:Word Ladder II 与 Alien Dictionary 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。

困难题为何不同

困难的 LeetCode 题目与中等题有两个关键区别:(1) 它们要求结合两种或更多算法技巧;(2) 仅从题目描述通常无法直接看出最优解——您必须透过表面描述,发现其底层的图结构或 DP 结构。单词接龙 II 和外星字典是典型的困难题,在 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}')

单词接龙 II:题目描述

单词接龙 II(LeetCode 126):给定一个起始单词、一个结束单词和一个单词列表,请找出从起始单词到结束单词的所有最短转换序列。每一步必须恰好转换一个字符,并且每个中间单词都必须存在于单词列表中。这比单词接龙 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]]')

单词接龙 II:BFS 阶段

在第 1 阶段,从起始单词开始逐层运行 BFS。在每一层中,我们找出所有相邻词(相差一个字符的单词)。我们记录每个单词首次到达时的层级(距起点的距离)。到达结束单词后,我们 NOT 立即停止——而是继续处理发现结束单词所在的整层,以确保探索所有最短路径。

关键是,我们要构建一个 parents 字典,将每个单词映射到所有能够在任意最短路径中位于它之前的单词集合。这就是第 2 阶段回溯时使用的图。

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}')

单词接龙 II:DFS 回溯阶段

在第 2 阶段,从结束单词开始进行 DFS 回溯,反向跟随 parents 映射。我们从结束单词向起始单词构建路径(最后再将路径反转)。当到达起始单词时,就找到了一条完整的最短路径。父节点映射保证找到的所有路径长度都是最小的——我们不可能“偏离”到更长的路径。

这种两阶段方法(用 BFS 确定层级,用 DFS 重建路径)是标准解法。BFS 的复杂度为 O(n × L × 26),其中 n = 单词列表大小,L = 单词长度;DFS 的复杂度为 O(K × L),其中 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']))

外星字典:题目描述

外星字典(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')

外星字典:构建图

第一步是提取约束:比较每一对相邻单词,找出第一个不同的字符,然后从较小字符向较大字符 add 一条有向边。如果一个单词是下一个单词的前缀,却比下一个单词更长(例如 '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)

外星字典:拓扑排序

构建图后,应用 Kahn 的 BFS 拓扑排序:将所有入度为 0(没有前置条件)的字符初始化到队列中。处理每个字符时,将其后继节点的入度减 1。当某个后继节点的入度变为 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)

处理边界情况:两道题目

单词接龙 II 和外星字典都有一些容易被忽略的边界情况,处理不当就会导致答案错误:

  • 单词接龙 II:beginWord 和 endWord 相同(返回 [[beginWord]] 或长度为 1 的路径)。endWord 不在 wordList 中(返回空结果)。不存在路径(返回空结果)。
  • 外星字典:duplicate 单词(不提取约束)。只有一个单词(返回所有不重复字符)。约束中存在环(返回 '')。某个单词是下一个单词的较长前缀(输入无效,返回 '')。所有字符都是孤立节点(返回任意顺序)。
# 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()

复杂度分析:两道题目

单词接龙 II 的复杂度:BFS 阶段的复杂度为 O(n × L × 26),其中 n = 列表中的单词数,L = 单词长度。对于每个 BFS 层级中的每个单词,我们会生成 26L 个候选单词,并在单词集合中检查它们是否存在(每次检查的复杂度为 O(1))。DFS 阶段的复杂度为 O(K × L),其中 K = 最短路径的数量(理论上可能呈指数增长)。

外星字典的复杂度:构建图的复杂度为 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()

模式总结:两个可复用模板

两道题目都教会了您可复用的模式。单词接龙 II = 用 BFS 计算距离 + 用 DFS 重建路径:当您需要在无权图中找出所有最短路径时,就会用到这种模式。在 BFS 期间构建父节点映射,然后从目标节点回溯到源节点。

外星字典 = 提取边 + 拓扑排序:当您得到一个有序序列,必须推断其底层排序规则时,就会用到这种模式。从相邻单词对中提取有向约束,然后应用 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 表格——困难题会以不明显的方式组合这些模块
  • 从示例开始:用一个小示例手动跟踪问题,以发现其底层结构
  • 验证子问题:实现第 1 阶段(构建图)后,打印图并手动验证,然后再进入第 2 阶段
# 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')

快速检查

请检查您对本课“数据结构与算法——编码面试准备”概念的理解。

课程回顾

本课您学到了:单词接龙 II 使用 BFS 构建包含所有最短路径前置节点的父节点映射,然后通过从结束单词到起始单词跟随父节点进行 DFS 回溯,从而枚举所有最短路径;外星字典从相邻单词对中提取有向约束,并应用 Kahn 的拓扑排序来排列字符,在检测到环时返回空字符串;以及困难题可以拆分为多个子问题——构建图、寻找距离和重建路径——每个子问题都可以使用熟悉的算法独立解决。您现在已经完成了完整的 DSA 面试准备课程。请将本课程中的每一种模式和技巧自信地应用到面试中。

常见问题解答

「困难问题 walkthrough:Word Ladder II 与 Alien Dictionary」课时是免费的吗?

是的 — 「困难问题 walkthrough:Word Ladder II 与 Alien Dictionary」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。

「困难问题 walkthrough:Word Ladder II 与 Alien Dictionary」这节课中我会学到什么?

端到端解决两个困难问题——使用 BFS + 回溯解决 Word Ladder II,使用拓扑排序解决 Alien Dictionary——并获得完整讲解。 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Coding Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「困难问题 walkthrough:Word Ladder II 与 Alien Dictionary」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Coding Interview Prep 课中编写并运行代码吗?

能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 模式识别速查表
  2. 限时模拟面试:简单与中等难度问题
  3. 处理边界情况与面试沟通
  4. 困难问题 walkthrough:Word Ladder II 与 Alien Dictionary
← 返回 Coding Interview Prep