​1，题目描述​

​英文描述​

​中文描述​

​2，解题思路​

​如何构建图？​

​3，AC代码​

​4，解题过程​

## 1，题目描述

### 英文描述

Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:

Only one letter can be changed at a time
Each transformed word must exist in the word list. Note that beginWord is not a transformed word.
Note:

Return an empty list if there is no such transformation sequence.
All words have the same length.
All words contain only lowercase alphabetic characters.
You may assume no duplicates in the word list.
You may assume beginWord and endWord are non-empty and are not the same.
Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
Example 2:

Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

Output: []

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.

### 中文描述

beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]

beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

## 2，解题思路

### 如何构建图？

• 取出队列中的一个字符串s1，将字符串中每一个字符依次用26个英文字母进行替换；
• 查找替换后的字符串s2是否存在于合法字符串集合dirc中（unordered_set<string> dirc(wordList.begin(), wordList.end())），存在即说明s1到s2转换关系有效；

• 每当一层节点扩展完毕后，比较两种方法的BFS队列中下一次需要扩展的节点数目，选择节点数目小的进行下一次扩展；
• 直到两种方法的BFS队列中出现相同的节点（说明整棵树已经打通，联通的路径即为起点到终点的最短路径），BFS过程结束；
• 【使用双向BFS的好处在于，每次扩展可以选择扩展节点数目最少的一个方向，这样可以有效地进行剪枝，使得目标状态（从起点到终点形成完整通路｝尽可能早地出现】

• 使用findFlag两队列是否存在相同的元素，reverseFlag时刻标记当前BFS遍历的方向（false为自顶向下，true为自底向上）；
• 集合dirc记录剩余的合法字符串，每次扩展前，先将这些合法字符串从dirc中删除；for(string s : bfsFromBegin)   dirc.erase(s);
• 始终对“自顶向下”bfsFromBegin队列进行判空操作（BFS算法中while(stack.empty()操作），只是利用reverseFlag将bfsFromBegin与bfsFromEnd进行调换，使得bfsFromBegin中始终保持扩展节点数较少；
• 根据reverseFlag进行构建树的过程：reverseFlag ? tree[s2].push_back(s1) : tree[s1].push_back(s2);// 构建树 并始终保持方向从beginWord指向endWord

while(!bfsFromBegin.empty()){ unordered_set<string> next; // 存放下一层需要遍历的节点（由于此处BFS的特殊性【一次性扩展所有节点】，所以不是直接添加到原先队列的末尾，而是借助next） for(string s : bfsFromBegin) // 遍历过的节点从set中删除 避免成环 dirc.erase(s); for(string s1 : bfsFromBegin){ // 扩展下一层的节点 for(int i = 0; i < s1.size(); ++i){ string s2 = s1; // s2记录由s1扩展而来字符串 !!!注意这条语句不要放错位置 for(char c = 'a'; c <= 'z'; ++c){ s2[i] = c; if(dirc.count(s2) == 0) continue; if(bfsFromEnd.count(s2)){ findFlag = true; // 找到双向BFS重合的字符串，BFS过程即可终止 }else{ next.insert(s2); // 将字符串加入扩展队列 } reverseFlag ? tree[s2].push_back(s1) : tree[s1].push_back(s2);// 构建树 并始终保持方向从beginWord指向endWord } } } bfsFromBegin = next; // 更新队列 if(bfsFromBegin.size() > bfsFromEnd.size()){ reverseFlag = !reverseFlag; // 取反 swap(bfsFromBegin, bfsFromEnd); // 交换BFS的队列 改变BFS的方向 } if(findFlag) break; // 双向BFS交汇 BFS过程终止 }

### 如何寻找最短路径？

void dfs(vector<string>& cur, string curWord, string endWord){ if(curWord == endWord){ ans.push_back(cur); return; } for(string s : tree[curWord]){ cur.push_back(s); dfs(cur, s, endWord); cur.pop_back(); } }

## 3，AC代码

`class Solution {public:    unordered_map<string, vector<string> > tree;    // 构建图    vector<vector<string> > ans;                    // 存放最终结果        void dfs(vector<string>& cur, string curWord, string endWord){        if(curWord == endWord){            ans.push_back(cur);            return;        }        for(string s : tree[curWord]){            cur.push_back(s);            dfs(cur, s, endWord);            cur.pop_back();        }    }    vector<vector<string> > findLadders(string beginWord, string endWord, vector<string> & wordList) {        if(wordList.size() == 0 || find(wordList.begin(), wordList.end(), endWord) == wordList.end()) return {};        unordered_set<string> bfsFromBegin{beginWord};                   // 自顶向下的BFS队列 !!!注意使用集合        unordered_set<string> bfsFromEnd{endWord};                       // 自底向上的BFS队列 !!!注意使用集合        unordered_set<string> dirc(wordList.begin(), wordList.end());    // 初始化字典 记录未被访问过的字符串 !!!注意初始化方式        bool findFlag = false, reverseFlag = false;                      // findFlag两队列是否存在相同的元素 reverseflag时刻标记当前BFS遍历的方向（false为自顶向下，true为自底向上）        while(!bfsFromBegin.empty()){            unordered_set<string> next;                                  // 存放下一层需要遍历的节点（由于此处BFS的特殊性【一次性扩展所有节点】，所以不是直接添加到原先队列的末尾，而是借助next）            for(string s : bfsFromBegin)                                 // 遍历过的节点从set中删除 避免成环                dirc.erase(s);            for(string s1 : bfsFromBegin){                               // 扩展下一层的节点                for(int i = 0; i < s1.size(); ++i){                    string s2 = s1;                                      // s2记录由s1扩展而来字符串 !!!注意这条语句不要放错位置                    for(char c = 'a'; c <= 'z'; ++c){                        s2[i] = c;                        if(dirc.count(s2) == 0) continue;                        if(bfsFromEnd.count(s2)){                            findFlag = true;                             // 找到双向BFS重合的字符串，BFS过程即可终止                        }else{                            next.insert(s2);                             // 将字符串加入扩展队列                        }                        reverseFlag ? tree[s2].push_back(s1) : tree[s1].push_back(s2);// 构建树 并始终保持方向从beginWord指向endWord                                              }                }            }            bfsFromBegin = next;                                        // 更新队列            if(bfsFromBegin.size() > bfsFromEnd.size()){                reverseFlag = !reverseFlag;                             // 取反                 swap(bfsFromBegin, bfsFromEnd);                         // 交换BFS的队列 改变BFS的方向            }            if(findFlag) break;                                         // 双向BFS交汇 BFS过程终止        }        vector<string> cur = {beginWord};        dfs(cur, beginWord, endWord);                                   // 遍历形成的树 得到起点到终点的路径        return ans;    }};`