【一天一大 lee】单词接龙 (难度:中等) - Day20201105

【一天一大 lee】单词接龙 (难度:中等) - Day20201105_第1张图片

题目:

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

  1. 每次转换只能改变一个字母。
  2. 转换过程中的中间单词必须是字典中的单词。

说明:

  • 如果不存在这样的转换序列,返回 0。
  • 所有单词具有相同的长度。
  • 所有单词只由小写字母组成。
  • 字典中不存在重复的单词。
  • 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例:

  1. 示例1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

输出: 5

解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的长度 5。
  1. 示例2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

输出: 0

解释: endWord "cog" 不在字典中,所以无法进行转换。

抛砖引玉

思路:

特殊情况: 如果字典中不包含endWord则直接返回0

本题可以从两个角度来思考解法:

  1. 收集wordList中每个单词完成一次转换对应的结果,
    再从beginWord中逐个字符尝试替换,直到找到endWord,返回最小的查找次数
  2. 从beginWord开始逐个使用a到z字符替换每个位置的字符,替换的结果在wordList中
    则记录替换后的字符和步数,
    再将替换后的字符逐个使用a到z字符替换每个位置的字符… 依次类推来加步数
    最后返回找到endWord,返回最小步数

广度优先搜索 + 优化建图

【一天一大 lee】单词接龙 (难度:中等) - Day20201105_第2张图片

  • 声明map记录wordList中每个单词被替换单个字符后对应的子集:*og -> “dog”,“log”,“cog”
  • 为了防止重复枚举,声明visitedMap通过哈希记录已经枚举过的单词不在重复参与枚举
/**
 * @param {string} beginWord
 * @param {string} endWord
 * @param {string[]} wordList
 * @return {number}
 */
var ladderLength = function(beginWord, endWord, wordList) {
  if (!wordList.includes(endWord)) return 0
  let map = new Map(),
	len = wordList.length;

  // 记录替换字符后的子集
  for (let i = 0; i < len; i++) {
	  const item = wordList[i],
	  		itemLen = wordList[i].length
    for (let j = 0; j < itemLen; j++) {
      const newStr = wordList[i].substring(0, j) + '*' + wordList[i].substring(j + 1, itemLen);
      if (!map.has(newStr)) map.set(newStr, [])
      map.get(newStr).push(wordList[i])
    }
  }
	// 从 beginWord开始枚举可能替换的结果
  let queue = [[beginWord, 1]],
    visitedMap = new Map([[beginWord, true]]);
  while (queue.length) {
	let [word, level] = queue.shift(),
		itemLen = word.length;
    for (let i = 0; i < itemLen; i++) {
      const newStr = word.substring(0, i) + '*' + word.substring(i + 1, itemLen);
      if (map.has(newStr)) {
        for (let j = 0; j < map.get(newStr).length; j++) {
		  const child = map.get(newStr)[j]
		//   遇到endWord终止
		  if (child === endWord) return level + 1
		//   避免重复枚举字符
          if (!visitedMap.has(child)) {
            visitedMap.set(child, true)
            queue.push([child, level + 1]);
          }
        }
      }
    }
  }
  return 0
};

广度优先搜索 + 转换单词

  • 题目限定单词只由小写字母组成,那么在转换字符时,只需从beginWord开始,
    遍历转换位置逐个替换成a到z的字符就可以枚举所有转换元素,记录每个转换后的元素和转到到其所需步骤。
  • 将转换后的元素再次推送到队列中继续枚举知道遇到endWord返回
/**
 * @param {string} beginWord
 * @param {string} endWord
 * @param {string[]} wordList
 * @return {number}
 */
var ladderLength = function(beginWord, endWord, wordList) {
  let wordSet  = new Set(wordList),
        queue = [[beginWord, 1]];
  while (queue.length) {
    let len = queue.length
    for (let i = 0; i < len; i++) {
      const [word, level] = queue.shift();
      if (word == endWord) return level +1
      for (let i = 0; i < word.length; i++) {
		//   枚举可能存在的转换
        for (let c = 1; c <= 26; c++) {
          const newWord = word.slice(0, i) + String.fromCharCode(97+c) + word.slice(i + 1)
          if (wordSet.has(newWord)) {
            queue.push([newWord, level + 1]);
            wordSet.delete(newWord);
          }
        }
      }
    }
  }
  return 0
};

注意: 上面广度优先搜索方法转换过程存在顺序,队列尾部的元素依赖队列头部元素先转接,则queue需要遵循队列先进先出原则(FIFO)

博客: 前端小书童

每天的每日一题,写的题解会同步更新到公众号一天一大 lee 栏目
欢迎关注留言

公众号:前端小书童

你可能感兴趣的:(一天一大leet,leetcode,算法,javascript,前端)