255 lines
9.7 KiB
Markdown
255 lines
9.7 KiB
Markdown
<p align="center">
|
||
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
|
||
<img src="../pics/训练营.png" width="1000"/>
|
||
</a>
|
||
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
|
||
|
||
|
||
# 127. 单词接龙
|
||
|
||
[力扣题目链接](https://leetcode.cn/problems/word-ladder/)
|
||
|
||
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
|
||
* 序列中第一个单词是 beginWord 。
|
||
* 序列中最后一个单词是 endWord 。
|
||
* 每次转换只能改变一个字母。
|
||
* 转换过程中的中间单词必须是字典 wordList 中的单词。
|
||
* 给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
|
||
|
||
|
||
示例 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" 不在字典中,所以无法进行转换。
|
||
|
||
|
||
# 思路
|
||
|
||
以示例1为例,从这个图中可以看出 hit 到 cog的路线,不止一条,有三条,一条是最短的长度为5,两条长度为6。
|
||
|
||

|
||
|
||
本题只需要求出最短长度就可以了,不用找出路径。
|
||
|
||
所以这道题要解决两个问题:
|
||
|
||
* 图中的线是如何连在一起的
|
||
* 起点和终点的最短路径长度
|
||
|
||
首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接。
|
||
|
||
然后就是求起点和终点的最短路径长度,**这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。
|
||
|
||
本题如果用深搜,会比较麻烦,要在到达终点的不同路径中选则一条最短路。 而广搜只要达到终点,一定是最短路。
|
||
|
||
另外需要有一个注意点:
|
||
|
||
* 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
|
||
* 本题给出集合是数组型的,可以转成set结构,查找更快一些
|
||
|
||
C++代码如下:(详细注释)
|
||
|
||
```CPP
|
||
class Solution {
|
||
public:
|
||
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
|
||
// 将vector转成unordered_set,提高查询速度
|
||
unordered_set<string> wordSet(wordList.begin(), wordList.end());
|
||
// 如果endWord没有在wordSet出现,直接返回0
|
||
if (wordSet.find(endWord) == wordSet.end()) return 0;
|
||
// 记录word是否访问过
|
||
unordered_map<string, int> visitMap; // <word, 查询到这个word路径长度>
|
||
// 初始化队列
|
||
queue<string> que;
|
||
que.push(beginWord);
|
||
// 初始化visitMap
|
||
visitMap.insert(pair<string, int>(beginWord, 1));
|
||
|
||
while(!que.empty()) {
|
||
string word = que.front();
|
||
que.pop();
|
||
int path = visitMap[word]; // 这个word的路径长度
|
||
for (int i = 0; i < word.size(); i++) {
|
||
string newWord = word; // 用一个新单词替换word,因为每次置换一个字母
|
||
for (int j = 0 ; j < 26; j++) {
|
||
newWord[i] = j + 'a';
|
||
if (newWord == endWord) return path + 1; // 找到了end,返回path+1
|
||
// wordSet出现了newWord,并且newWord没有被访问过
|
||
if (wordSet.find(newWord) != wordSet.end()
|
||
&& visitMap.find(newWord) == visitMap.end()) {
|
||
// 添加访问信息
|
||
visitMap.insert(pair<string, int>(newWord, path + 1));
|
||
que.push(newWord);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
};
|
||
```
|
||
|
||
当然本题也可以用双向BFS,就是从头尾两端进行搜索,大家感兴趣,可以自己去实现,这里就不再做详细讲解了。
|
||
|
||
# 其他语言版本
|
||
|
||
## Java
|
||
|
||
```java
|
||
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
|
||
HashSet<String> wordSet = new HashSet<>(wordList); //转换为hashset 加快速度
|
||
if (wordSet.size() == 0 || !wordSet.contains(endWord)) { //特殊情况判断
|
||
return 0;
|
||
}
|
||
Queue<String> queue = new LinkedList<>(); //bfs 队列
|
||
queue.offer(beginWord);
|
||
Map<String, Integer> map = new HashMap<>(); //记录单词对应路径长度
|
||
map.put(beginWord, 1);
|
||
|
||
while (!queue.isEmpty()) {
|
||
String word = queue.poll(); //取出队头单词
|
||
int path = map.get(word); //获取到该单词的路径长度
|
||
for (int i = 0; i < word.length(); i++) { //遍历单词的每个字符
|
||
char[] chars = word.toCharArray(); //将单词转换为char array,方便替换
|
||
for (char k = 'a'; k <= 'z'; k++) { //从'a' 到 'z' 遍历替换
|
||
chars[i] = k; //替换第i个字符
|
||
String newWord = String.valueOf(chars); //得到新的字符串
|
||
if (newWord.equals(endWord)) { //如果新的字符串值与endWord一致,返回当前长度+1
|
||
return path + 1;
|
||
}
|
||
if (wordSet.contains(newWord) && !map.containsKey(newWord)) { //如果新单词在set中,但是没有访问过
|
||
map.put(newWord, path + 1); //记录单词对应的路径长度
|
||
queue.offer(newWord);//加入队尾
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return 0; //未找到
|
||
}
|
||
```
|
||
|
||
## Python
|
||
```
|
||
class Solution:
|
||
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
|
||
wordSet = set(wordList)
|
||
if len(wordSet)== 0 or endWord not in wordSet:
|
||
return 0
|
||
mapping = {beginWord:1}
|
||
queue = deque([beginWord])
|
||
while queue:
|
||
word = queue.popleft()
|
||
path = mapping[word]
|
||
for i in range(len(word)):
|
||
word_list = list(word)
|
||
for j in range(26):
|
||
word_list[i] = chr(ord('a')+j)
|
||
newWord = "".join(word_list)
|
||
if newWord == endWord:
|
||
return path+1
|
||
if newWord in wordSet and newWord not in mapping:
|
||
mapping[newWord] = path+1
|
||
queue.append(newWord)
|
||
return 0
|
||
```
|
||
## Go
|
||
```go
|
||
func ladderLength(beginWord string, endWord string, wordList []string) int {
|
||
wordMap, que, depth := getWordMap(wordList, beginWord), []string{beginWord}, 0
|
||
for len(que) > 0 {
|
||
depth++
|
||
qLen := len(que) // 单词的长度
|
||
for i := 0; i < qLen; i++ {
|
||
word := que[0]
|
||
que = que[1:] // 首位单词出队
|
||
candidates := getCandidates(word)
|
||
for _, candidate := range candidates {
|
||
if _, exist := wordMap[candidate]; exist { // 用生成的结果集去查询
|
||
if candidate == endWord {
|
||
return depth + 1
|
||
}
|
||
delete(wordMap, candidate) // 删除集合中的用过的结果
|
||
que = append(que, candidate)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
|
||
// 获取单词Map为后续的查询增加速度
|
||
func getWordMap(wordList []string, beginWord string) map[string]int {
|
||
wordMap := make(map[string]int)
|
||
for i, word := range wordList {
|
||
if _, exist := wordMap[word]; !exist {
|
||
if word != beginWord {
|
||
wordMap[word] = i
|
||
}
|
||
}
|
||
}
|
||
return wordMap
|
||
}
|
||
|
||
// 用26个英文字母分别替换掉各个位置的字母,生成一个结果集
|
||
func getCandidates(word string) []string {
|
||
var res []string
|
||
for i := 0; i < 26; i++ {
|
||
for j := 0; j < len(word); j++ {
|
||
if word[j] != byte(int('a')+i) {
|
||
res = append(res, word[:j]+string(int('a')+i)+word[j+1:])
|
||
}
|
||
}
|
||
}
|
||
return res
|
||
}
|
||
```
|
||
|
||
## JavaScript
|
||
```javascript
|
||
var ladderLength = function(beginWord, endWord, wordList) {
|
||
// 将wordList转成Set,提高查询速度
|
||
const wordSet = new Set(wordList);
|
||
// Set元素个数为0 或者 endWord没有在wordSet出现,直接返回0
|
||
if (wordSet.size === 0 || !wordSet.has(endWord)) return 0;
|
||
// 记录word是否访问过
|
||
const visitMap = new Map();// <word, 查询到这个word路径长度>
|
||
// 初始化队列
|
||
const queue = [];
|
||
queue.push(beginWord);
|
||
// 初始化visitMap
|
||
visitMap.set(beginWord, 1);
|
||
|
||
while(queue.length !== 0){
|
||
let word = queue.shift(); // 删除队首元素,将它的值存放在word
|
||
let path = visitMap.get(word); // 这个word的路径长度
|
||
for(let i = 0; i < word.length; i++){ // 遍历单词的每个字符
|
||
for (let c = 97; c <= 122; c++) { // 对应26个字母ASCII值 从'a' 到 'z' 遍历替换
|
||
// 拼串得到新的字符串
|
||
let newWord = word.slice(0, i) + String.fromCharCode(c) + word.slice(i + 1);
|
||
if(newWord === endWord) return path + 1; // 找到了end,返回path+1
|
||
// wordSet出现了newWord,并且newWord没有被访问过
|
||
if(wordSet.has(newWord) && !visitMap.has(newWord)) {
|
||
// 添加访问信息
|
||
visitMap.set(newWord, path + 1);
|
||
queue.push(newWord);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
};
|
||
```
|
||
|
||
|
||
<p align="center">
|
||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||
</a>
|