【JS每日一算法】🟥120. 单词接龙 II(广度优先+深度优先)

292 阅读2分钟

按字典 wordList 完成从单词 beginWord 到单词 endWord 转化,一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk 这样的单词序列,并满足:

  • 每对相邻的单词之间仅有单个字母不同。
  • 转换过程中的每个单词 si1 <= i <= k)必须是字典 wordList 中的单词。注意,beginWord 不必是字典 wordList 中的单词。
  • sk == endWord

给你两个单词 beginWord 和 endWord ,以及一个字典 wordList 。请你找出并返回所有从 beginWord 到 endWord 的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。每个序列都应该以单词列表 **[beginWord, s1, s2, ..., sk] 的形式返回。

 

提示:

  • 1 <= beginWord.length <= 5
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 500
  • wordList[i].length == beginWord.length
  • beginWordendWord 和 wordList[i] 由小写英文字母组成
  • beginWord != endWord
  • wordList 中的所有单词 互不相同

示例 1:

输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
解释: 存在 2 种最短的转换序列:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"

示例 2:

输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典 wordList 中,所以不存在符合要求的转换序列。

题解:

个人博客

更多JS版本题解点击链接关注该仓库👀



/**
 * @description: 广度优先+深度优先
 * 引用leetcode:笨猪爆破组
 * @author: JunLiangWang
 * @param {*} beginWord  开始单词
 * @param {*} endWord    结束单词
 * @param {*} wordList   单词列表
 * @return {*}
 */
function BFSAndDFS (beginWord, endWord, wordList)  {
    const wordSet = new Set(wordList);
    wordSet.add(beginWord);               // 这个其实要不要都行
    if (!wordSet.has(endWord)) return []; // 单词表中没有终点词,无法变到终点词
  
    const levelMap = new Map();           // 存放图中的单词所在的层
    const wordMap = new Map();            // 存放图中的单词的邻接单词
    const visited = new Set();            // 记录访问过的节点
    const queue = [beginWord];            // 维护一个队列,初始放入起点词
    visited.add(beginWord);               // 入列即访问,存入visited
  
    let finished = false;             // 是否存在变化到终点词的路径
    let level = 0;             
    levelMap.set(beginWord, 0);       // 起始词的level为0
  
    while (queue.length) {            // 队列空了,所有邻接节点就遍历完了
      const levelSize = queue.length; // 当前level的单词个数
      level++;                        // 遍历当前层的单词,level+1
      for (let i = 0; i < levelSize; i++) { // 当前层的单词逐个出列考察
        const word = queue.shift();   // 当前出列的单词
  
        for (let i = 0; i < word.length; i++) {  // 遍历单词的所有字符
          for (let c = 97; c <= 122; c++) {      // 遍历26个字母字符
            const newWord = word.slice(0, i) + String.fromCharCode(c) + word.slice(i + 1);
            if (!wordSet.has(newWord)) continue; // 不是单词表中的单词就忽略
            if (wordMap.has(newWord))            // 已经存在于wordMap
              wordMap.get(newWord).push(word);   // 对应的数组推入出列的单词
            else                                 // 初始化一个数组
              wordMap.set(newWord, [word]);      // 并放入“父单词”
  
            if (visited.has(newWord)) continue; // 该新单词已经访问过就忽略
            if (newWord == endWord)             // 遇到了终点词
              finished = true;                  // 存在抵达终点词的路径
  
            levelMap.set(newWord, level);       // 记录这个单词的level
            queue.push(newWord);                // 该新单词是下一层的,入列
            visited.add(newWord);               // 入列即访问,记录一下
          }
        }
      }
    }
    if (!finished) return []; // 无法到达终点词,返回[]
  
    const res = [];
    const dfs = (path, beginWord, word) => {
      if (word == beginWord) {          // 当前遍历的word,和起始词相同
        res.push([beginWord, ...path]); // 将当前路径推入res数组,加上起始词
        return;                        
      }
      path.unshift(word);        // 将当前单词加入到path数组的开头
      if (wordMap.get(word)) {   // 当前单词有对应的“父单词”们
        for (const parent of wordMap.get(word)) { // 遍历“父单词”们
          if (levelMap.get(parent) + 1 == levelMap.get(word)) { // 满足要求的
            dfs(path, beginWord, parent);                       // 递归dfs
          }
        }
      }
      path.shift(); // 回溯,撤销选择,将path数组开头的单词弹出
    }
    dfs([], beginWord, endWord); // dfs的入口
    return res;
  };