力扣_字符串9—单词接龙I、II

题目

按字典 w o r d L i s t wordList wordList 完成从单词 b e g i n W o r d beginWord beginWord 到单词 e n d W o r d endWord endWord 转化,一个表示此过程的 转换序列 是形式上像 b e g i n W o r d − > s 1 − > s 2 − > . . . − > s k beginWord -> s1 -> s2 -> ... -> sk beginWord>s1>s2>...>sk 这样的单词序列,并满足:

  • 每对相邻的单词之间仅有单个字母不同。
  • 转换过程中的每个单词 s i ( 1 < = i < = k ) si(1 <= i <= k) si(1<=i<=k)必须是字典 w o r d L i s t wordList wordList 中的单词。注意, b e g i n W o r d beginWord beginWord 不必是字典 w o r d L i s t wordList wordList 中的单词。
  • s k = = e n d W o r d sk == endWord sk==endWord
  • 给你两个单词 b e g i n W o r d beginWord beginWord e n d W o r d endWord endWord ,以及一个字典 w o r d L i s t wordList wordList 。请你找出并返回所有从 b e g i n W o r d beginWord beginWord e n d W o r d endWord endWord 的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。每个序列都应该以单词列表 [ b e g i n W o r d , s 1 , s 2 , . . . , s k ] [beginWord, s1, s2, ..., sk] [beginWord,s1,s2,...,sk] 的形式返回。

示例:

  • 输入: b e g i n W o r d = " h i t " , e n d W o r d = " c o g " , w o r d L i s t = [ " h o t " , " d o t " , " d o g " , " l o t " , " l o g " , " c o g " ] beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] beginWord="hit",endWord="cog",wordList=["hot","dot","dog","lot","log","cog"]
  • 输出: [ [ " h i t " , " h o t " , " d o t " , " d o g " , " c o g " ] , [ " h i t " , " h o t " , " l o t " , " l o g " , " c o g " ] ] [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]] [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
  • 解释:存在 2 2 2 种最短的转换序列:
    " h i t " − > " h o t " − > " d o t " − > " d o g " − > " c o g " "hit" -> "hot" -> "dot" -> "dog" -> "cog" "hit">"hot">"dot">"dog">"cog"
    " h i t " − > " h o t " − > " l o t " − > " l o g " − > " c o g " "hit" -> "hot" -> "lot" -> "log" -> "cog" "hit">"hot">"lot">"log">"cog"

方法

  • 广度优先
    • 我们可以把每个单词都抽象为一个点,如果两个单词可以只改变一个字母进行转换,那么说明他们之间有一条双向边。因此我们只需要把满足转换条件的点相连,就形成了一张图。
    • 基于该图,我们以 b e g i n W o r d beginWord beginWord 为图的起点,以 e n d W o r d endWord endWord 为终点进行广度优先搜索,寻找 b e g i n W o r d beginWord beginWord e n d W o r d endWord endWord 的最短路径。
    • 建图:依据朴素的思路,我们可以枚举每一对单词的组合,判断它们是否恰好相差一个字符,以判断这两个单词对应的节点是否能够相连。但是这样效率太低,我们可以优化建图。
      • 具体地,我们可以创建虚拟节点。例如对于单词 hit,我们创建三个虚拟节点 *it、h*t、hi*,并让 hit 向这三个虚拟节点分别连一条边即可。如果一个单词能够转化为 hit,那么该单词必然会连接到这三个虚拟节点之一。对于每一个单词,我们枚举它连接到的虚拟节点,把该单词对应的 id 与这些虚拟节点对应的 id 相连即可。
    • 最后我们将起点加入队列开始广度优先搜索,当搜索到终点时,我们就找到了最短路径的长度。注意因为添加了虚拟节点,所以我们得到的距离为实际最短路径长度的两倍。同时我们并未计算起点对答案的贡献,所以我们应当返回距离的一半再加一的结果。
    • 因为要返回转换序列,所以每次搜索时要将 每个单词是从哪些单词扩展而来 记录下来(代码中的 f r o m from from),最后回溯得到最短路径

代码

class Solution {
public:
    int id = 0;
    vector<vector<int>> graph;
    map<string, int> maps;
    map<int, string> maps1;

    bool add_node(string& word){
        if(maps.find(word) == maps.end()){
            maps[word] = id;
            maps1[id] = word;
            id++;
            graph.emplace_back();
            return true;
        }
        else
            return false;
    }
    void add_edge(string& word){
        if(add_node(word)){
            int id2 = maps[word];
            for(int i = 0; i < word.size(); i++){
                string temp = word;
                temp[i] = '*';
                add_node(temp);
                int id1 = maps[temp];
                graph[id1].push_back(id2);
                graph[id2].push_back(id1);
            }
        }
    }
    void backtrack(vector<vector<string>> &res, const string &Node, unordered_map<string, set<string>> &from,
             vector<string> &path) {
        if (from[Node].empty()) {
            res.push_back({path.rbegin(), path.rend()});
            return;
        }
        for (const string &Parent: from[Node]) {
            if(Parent.find('*') == string::npos)
                path.push_back(Parent);
            backtrack(res, Parent, from, path);
            if(Parent.find('*') == string::npos)
                path.pop_back();
        }
    }
    // int ladderLength(string beginWord, string endWord, vector& wordList) {
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        vector<vector<string>> res;
        for(int i = 0; i < wordList.size(); i++){
            add_edge(wordList[i]);
        }
        add_edge(beginWord);
        if(maps.find(endWord) == maps.end())
            return res;
        queue<int> que;
        que.push(maps[beginWord]);
        vector<int> dis(id, INT_MAX);
        dis[maps[beginWord]] = 0;
        unordered_map<string, set<string>> from = {{beginWord, {}}};
        bool found = false;
        while(!que.empty()){
            int id_temp = que.front();
            que.pop();
            if(id_temp == maps[endWord]){
                // return dis[id_temp]/2+1;
                found = true;
                break;
            }
            for(int i = 0; i < graph[id_temp].size(); i++){
                // 不懂就手写一遍广度优先的流程
                if(dis[graph[id_temp][i]] == INT_MAX){
                    from[maps1[graph[id_temp][i]]].insert(maps1[id_temp]);
                    que.push(graph[id_temp][i]);
                    dis[graph[id_temp][i]] = dis[id_temp] + 1;
                }
                else if(dis[id_temp]+1 == dis[graph[id_temp][i]]){
                    from[maps1[graph[id_temp][i]]].insert(maps1[id_temp]);
                }
            }
        }
        if (found) {
            vector<string> Path = {endWord};
            backtrack(res, endWord, from, Path);
        }
        return res;
    }
};

你可能感兴趣的:(leetcode,c#,算法)