返回

LeetCode 剖析:DFS 和 BFS 的艺术——最小基因变化

前端

导言

踏上 LeetCode 算法学习之旅,我们今天将直面一道困难级别的题目:433. 最小基因变化。这道题考察了算法中的两个核心概念:深度优先搜索 (DFS) 和广度优先搜索 (BFS)。让我们深入探讨解题思路,掌握这些算法的精妙之处。

题目

科学家正在研究一种由 n 个基因组成的疾病。一开始,患者只有 1 个突变基因。目标是 使用最少的基因突变将患者的基因组变为正常状态。

给定一个基因组的初始状态和目标状态,以及基因突变规则,求出将基因组从初始状态变为目标状态所需的最小基因突变数

算法选择

解决这道题的关键在于选择合适的算法。DFS 和 BFS 都是遍历图的经典算法,但它们的特性不同:

  • DFS: 深度优先搜索沿着当前路径深入搜索,直到找到解或穷举所有可能。
  • BFS: 广度优先搜索逐层探索所有路径,以确保找到最短路径。

DFS 的优势

对于这道题,DFS 的优势在于它的深度优先特性,可以快速找到从初始状态到目标状态的路径,即使路径较长。而且,DFS 不需要存储所有已访问过的状态,从而节省了内存空间。

解题思路

我们的解题思路如下:

  1. 将基因突变规则构建为邻接表,每个基因对应一个顶点,突变规则对应顶点之间的边。
  2. 从初始基因出发,使用 DFS 遍历邻接表,记录每一步的基因突变数。
  3. 当 DFS 到达目标基因时,记录此时的突变数。
  4. 在 DFS 过程中,维护一个哈希表记录已访问过的基因,避免重复访问。

BFS 的补充作用

虽然 DFS 对于这道题是主要算法,但 BFS 也可以在特定情况下提供帮助。如果基因突变规则的图非常密集,即每个基因与许多其他基因相连,则 BFS 的广度优先特性可以帮助我们更有效地探索所有路径。

代码实现

import collections

def min_mutation(start, end, bank):
  """
  使用 DFS 算法求解最小基因突变数。

  参数:
    start (str): 初始基因。
    end (str): 目标基因。
    bank (list[str]): 突变规则。
  
  返回:
    int: 最小基因突变数。
  """

  # 构建邻接表
  graph = collections.defaultdict(list)
  for gene in bank:
    for i in range(len(gene)):
      for c in 'ACGT':
        if gene[i] != c:
          graph[gene[:i] + c + gene[i+1:]].append(gene)

  # 使用 DFS 遍历邻接表
  visited = set()
  queue = [(start, 0)]

  while queue:
    gene, mutation = queue.pop(0)

    if gene == end:
      return mutation
    
    if gene not in visited:
      visited.add(gene)
      for neighbor in graph[gene]:
        queue.append((neighbor, mutation + 1))

  # 未找到路径
  return -1

总结

通过对 DFS 和 BFS 特性的深入理解,我们可以针对不同场景选择合适的算法,从而高效解决 LeetCode 算法难题。433. 最小基因变化一题正是 DFS 和 BFS 优势的完美体现。掌握这些算法的精髓,我们将所向披靡,征服更多 LeetCode 挑战!