返回

LeetCode 433:最小基因变化的快速解法

前端

在这篇博文中,我们将探讨 LeetCode 上一道颇具挑战性的问题——433. 最小基因变化。我们将使用双向 BFS(广度优先搜索)算法,这是一种高效且优雅的解决方法。让我们深入探讨这道题目的解题思路,了解为什么双向 BFS 是破解它的最佳途径。

双向 BFS 算法

双向 BFS 是一种算法,它从问题空间的两端开始搜索,同时向中间推进。当两端相遇时,它们就找到了连接起点和终点的最短路径。在 LeetCode 433 中,我们将从 start 和 end 两个单词开始搜索,向中间推进,直到两端的搜索相遇。

JavaScript 实现

以下是用 JavaScript 实现的双向 BFS 算法:

function minMutation(start, end, bank) {
  // BFS 队列
  const startQueue = [start];
  const endQueue = [end];
  // 已访问单词
  const visited = new Set();
  // 步数
  let steps = 0;

  while (startQueue.length && endQueue.length) {
    // 处理较小队列
    const queue = startQueue.length < endQueue.length ? startQueue : endQueue;

    // 遍历队列中的单词
    for (let i = 0; i < queue.length; i++) {
      const word = queue[i];
      if (visited.has(word)) continue;
      visited.add(word);

      // 如果单词相等,则找到最短路径
      if (word === end) return steps;

      // 生成变异单词
      const nextWords = getNextWords(word);

      // 将变异单词加入队列
      for (const nextWord of nextWords) {
        if (!visited.has(nextWord)) {
          queue.push(nextWord);
        }
      }
    }

    // 从队列中移除已处理的单词
    queue.splice(0, queue.length);

    // 增加步数
    steps++;
  }

  // 未找到最短路径
  return -1;
}

function getNextWords(word) {
  // 允许变异的位数
  const allowedMutations = 1;
  const nextWords = [];

  // 遍历单词中的每个字符
  for (let i = 0; i < word.length; i++) {
    // 遍历所有可能的变异
    for (const char of 'ACGT') {
      // 跳过相同的字符
      if (char === word[i]) continue;

      // 生成变异单词
      const newWord = word.substring(0, i) + char + word.substring(i + 1);

      // 判断变异单词是否有效
      if (bank.includes(newWord)) {
        nextWords.push(newWord);
      }
    }
  }

  return nextWords;
}

优势

双向 BFS 在解决 LeetCode 433 时具有以下优势:

  • 优化遍历次数: 每次推进都选取两端队列中较小者,这可以大大减少遍历的次数。
  • 高效队列使用: BFS 需要使用队列,当一端队列元素的下一个变化单词存在于另一端的队列中时,即可认为两者相遇。使用 Map 判断单词是否存在于队列中,可以进一步优化搜索。
  • 直观且易于理解: 双向 BFS 是一种直观且易于理解的算法,即使对于初学者也是如此。

总结

双向 BFS 是一种高效且优雅的方法,可以解决 LeetCode 433:最小基因变化问题。通过从起点和终点同时向中间推进,我们可以有效地找到最短路径。JavaScript 实现简洁且易于理解,使这一算法成为解决此类问题的理想选择。