每天一道leetcode:127. 单词接龙(图论&困难&建图&广度优先遍历)

今日份题目:

字典 wordList 中从单词 beginWordendWord转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk

  • 每一对相邻的单词只差一个字母。

  • 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。

  • sk == endWord

给你两个单词 beginWordendWord 和一个字典 wordList ,返回 beginWordendWord最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 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 <= beginWord.length <= 10

  • endWord.length == beginWord.length

  • 1 <= wordList.length <= 5000

  • wordList[i].length == beginWord.length

  • beginWordendWordwordList[i] 由小写英文字母组成

  • beginWord != endWord

  • wordList 中的所有字符串 互不相同

题目思路

首先考虑建图,其次考虑遍历图。

我们把单词抽象成虚拟点,然后将单词拆分成只改变一个字母的虚拟节点。如:对于单词 hit,我们创建三个虚拟节点 * it、h * t、hi *,并让 hit 向这三个虚拟节点分别连一条边。如果一个单词能够转化为 hit,那么该单词必然会连接到这三个虚拟节点之一。对于每个单词,枚举它连接到的所有的虚拟节点,把该单词节点与这些虚拟节点相连即可。

最后进行广度优先遍历,当遍历到终点时,就找到了最短路径的长度。

注意:由于添加了虚拟节点,得到的距离为实际最短路径长度的两倍。同时由于并未计算起点的贡献,所以应当返回距离的一半再加一。

代码

class Solution 
{
public:
    unordered_map<string,int> dict; //存放字典内容
    vector<vector<int>> edge;
    int nodeNum=0; //用来标记是第几个点

    void wordNode(string& word) //把单词抽象为一个点
    {
        if(!dict.count(word)) //字典中没有这个点
        {
            dict[word]=nodeNum;
            nodeNum++;
            edge.emplace_back();
        }
    }

    void Edge(string& word) //如果两个单词可以通过只改变一个字母实现转换,表示两点之间有条边
    {
        wordNode(word); //建立点
        int u=dict[word]; //边的第一个端点
        for(char& c:word) //遍历所有位置,得到各虚拟节点*it、h*t、hi*
        {
            //hit<->*it,hit<->h*t,hit<->hi*
            char temp=c;
            c='*';
            wordNode(word); //分别建立虚拟点
            int v=dict[word]; //边的第二个端点
            //建立无向图的边
            edge[u].push_back(v);
            edge[v].push_back(u);
            c=temp; //恢复原单词
        }
    }

    int ladderLength(string beginWord, string endWord, vector<string>& wordList) 
    {
        for(string& word:wordList) //对字典中的每个单词建立边
        {
            Edge(word);
        }
        Edge(beginWord); //对初始单词建立边
        if(!dict.count(endWord)) return 0; //无法变化为结果的样子
        //注意,由于是在建立边的过程中建立虚拟点,所以一定要先建好边再判断能否变化为结果单词
        vector<int> dis(nodeNum,INT_MAX); //记录变化步数
        int begin=dict[beginWord],end=dict[endWord]; //开始的点和结束的点
        dis[begin]=0;
        queue<int> p;
        p.push(begin);
        //bfs
        while(!p.empty()) 
        {
            int cur=p.front();
            p.pop();
            if(cur==end) 
            {
                //因为添加了虚拟节点,所以得到的距离为实际最短路径长度的两倍;同时由于未计算起点的贡献,所以应当返回距离的一半再加一
                return dis[end]/2+1;
            }
            for(int& c:edge[cur]) //遍历当前单词可以进行的变化
            {
                //如果一个单词能够转化为 hit,那么该单词必然会连接到上述三个虚拟节点之一
                if(dis[c]==INT_MAX) //还没到过这个单词变化情况
                {
                    dis[c]=dis[cur]+1; //这种情况是在原来情况下变一个字母,步数加一
                    p.push(c);
                }
            }
        }
        return 0;
    }
};

提交结果

欢迎大家在评论区讨论,如有不懂的部分,欢迎在评论区留言!

更新不易,宝子们点个赞支持下,谢谢!

每天一道leetcode,大家一起在评论区打卡呀!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值