题目:
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回 0。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例:
- 示例1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出: 5
解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度 5。
- 示例2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: 0
解释: endWord "cog" 不在字典中,所以无法进行转换。
抛砖引玉
思路:
特殊情况: 如果字典中不包含endWord则直接返回0
本题可以从两个角度来思考解法:
- 收集wordList中每个单词完成一次转换对应的结果, 再从beginWord中逐个字符尝试替换,直到找到endWord,返回最小的查找次数
- 从beginWord开始逐个使用a到z字符替换每个位置的字符,替换的结果在wordList中 则记录替换后的字符和步数, 再将替换后的字符逐个使用a到z字符替换每个位置的字符... 依次类推来加步数 最后返回找到endWord,返回最小步数
广度优先搜索 优化建图
抛砖引玉
- 声明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)