赞
踩
转载(https://leetcode-cn.com/problems/word-ladder/solution/dan-ci-jie-long-by-leetcode-solution/)
方法一:广度优先搜索 + 优化建图
思路
本题要求的是最短转换序列的长度,看到最短首先想到的就是广度优先搜索。想到广度优先搜索自然而然的就能想到图,但是本题并没有直截了当的给出图的模型,因此我们需要把它抽象成图的模型。
我们可以把每个单词都抽象为一个点,如果两个单词可以只改变一个字母进行转换,那么说明他们之间有一条双向边。因此我们只需要把满足转换条件的点相连,就形成了一张图。
基于该图,我们以 beginWord 为图的起点,以 endWord 为终点进行广度优先搜索,寻找 beginWord 到 endWord 的最短路径。
算法
基于上面的思路我们考虑如何编程实现。
首先为了方便表示,我们先给每一个单词标号,即给每个单词分配一个 id。创建一个由单词 word 到 id 对应的映射 wordId,并将 beginWord 与 wordList 中所有的单词都加入这个映射中。之后我们检查 endWord 是否在该映射内,若不存在,则输入无解。我们可以使用哈希表实现上面的映射关系。
然后我们需要建图,依据朴素的思路,我们可以枚举每一对单词的组合,判断它们是否恰好相差一个字符,以判断这两个单词对应的节点是否能够相连。但是这样效率太低,我们可以优化建图。
具体地,我们可以创建虚拟节点。对于单词 hit,我们创建三个虚拟节点 it、ht、hi*,并让 hit 向这三个虚拟节点分别连一条边即可。如果一个单词能够转化为 hit,那么该单词必然会连接到这三个虚拟节点之一。对于每一个单词,我们枚举它连接到的虚拟节点,把该单词对应的 id 与这些虚拟节点对应的 id 相连即可。
最后我们将起点加入队列开始广度优先搜索,当搜索到终点时,我们就找到了最短路径的长度。注意因为添加了虚拟节点,所以我们得到的距离为实际最短路径长度的两倍。同时我们并未计算起点对答案的贡献,所以我们应当返回距离的一半再加一的结果。
class Solution { public: unordered_map<string, int> wordId; vector<vector<int>> edge; int nodeNum = 0; void addWord(string& word) { if (!wordId.count(word)) { wordId[word] = nodeNum++; edge.emplace_back(); } } void addEdge(string& word) { addWord(word); int id1 = wordId[word]; for (char& it : word) { char tmp = it; it = '*'; addWord(word); int id2 = wordId[word]; edge[id1].push_back(id2); edge[id2].push_back(id1); it = tmp; } } int ladderLength(string beginWord, string endWord, vector<string>& wordList) { for (string& word : wordList) { addEdge(word); } addEdge(beginWord); if (!wordId.count(endWord)) { return 0; } vector<int> dis(nodeNum, INT_MAX); int beginId = wordId[beginWord], endId = wordId[endWord]; dis[beginId] = 0; queue<int> que; que.push(beginId); while (!que.empty()) { int x = que.front(); que.pop(); if (x == endId) { return dis[endId] / 2 + 1; } for (int& it : edge[x]) { if (dis[it] == INT_MAX) { dis[it] = dis[x] + 1; que.push(it); } } } return 0; } };
复杂度分析
时间复杂度:O(N \times C^2)O(N×C
2
)。其中 NN 为 wordList 的长度,CC 为列表中单词的平均长度。
建图过程中,对于每一个单词,我们需要枚举它连接到的所有虚拟节点,时间复杂度为 O©O©,将这些单词加入到哈希表中,时间复杂度为 O(N \times C)O(N×C),因此总时间复杂度为 O(N \times C)O(N×C)。
广度优先搜索的时间复杂度最坏情况下是 O(N \times C)O(N×C)。每一个单词需要拓展出 O©O© 个虚拟节点,因此节点数 O(N \times C)O(N×C)。
空间复杂度:O(N \times C^2)O(N×C
2
)。其中 NN 为 wordList 的长度,CC 为列表中单词的平均长度。哈希表中包含 O(N \times C)O(N×C) 个节点,每个节点占用空间 O©O©,因此总的空间复杂度为 O(N \times C^2)O(N×C
2
)。
方法二:双向广度优先搜索
思路及解法
根据给定字典构造的图可能会很大,而广度优先搜索的搜索空间大小依赖于每层节点的分支数量。假如每个节点的分支数量相同,搜索空间会随着层数的增长指数级的增加。考虑一个简单的二叉树,每一层都是满二叉树的扩展,节点的数量会以 22 为底数呈指数增长。
如果使用两个同时进行的广搜可以有效地减少搜索空间。一边从 beginWord 开始,另一边从 endWord 开始。我们每次从两边各扩展一层节点,当发现某一时刻两边都访问过同一顶点时就停止搜索。这就是双向广度优先搜索,它可以可观地减少搜索空间大小,从而提高代码运行效率。
class Solution { public: unordered_map<string, int> wordId; vector<vector<int>> edge; int nodeNum = 0; void addWord(string& word) { if (!wordId.count(word)) { wordId[word] = nodeNum++; edge.emplace_back(); } } void addEdge(string& word) { addWord(word); int id1 = wordId[word]; for (char& it : word) { char tmp = it; it = '*'; addWord(word); int id2 = wordId[word]; edge[id1].push_back(id2); edge[id2].push_back(id1); it = tmp; } } int ladderLength(string beginWord, string endWord, vector<string>& wordList) { for (string& word : wordList) { addEdge(word); } addEdge(beginWord); if (!wordId.count(endWord)) { return 0; } vector<int> disBegin(nodeNum, INT_MAX); int beginId = wordId[beginWord]; disBegin[beginId] = 0; queue<int> queBegin; queBegin.push(beginId); vector<int> disEnd(nodeNum, INT_MAX); int endId = wordId[endWord]; disEnd[endId] = 0; queue<int> queEnd; queEnd.push(endId); while (!queBegin.empty() && !queEnd.empty()) { int queBeginSize = queBegin.size(); for (int i = 0; i < queBeginSize; ++i) { int nodeBegin = queBegin.front(); queBegin.pop(); if (disEnd[nodeBegin] != INT_MAX) { return (disBegin[nodeBegin] + disEnd[nodeBegin]) / 2 + 1; } for (int& it : edge[nodeBegin]) { if (disBegin[it] == INT_MAX) { disBegin[it] = disBegin[nodeBegin] + 1; queBegin.push(it); } } } int queEndSize = queEnd.size(); for (int i = 0; i < queEndSize; ++i) { int nodeEnd = queEnd.front(); queEnd.pop(); if (disBegin[nodeEnd] != INT_MAX) { return (disBegin[nodeEnd] + disEnd[nodeEnd]) / 2 + 1; } for (int& it : edge[nodeEnd]) { if (disEnd[it] == INT_MAX) { disEnd[it] = disEnd[nodeEnd] + 1; queEnd.push(it); } } } } return 0; } };
复杂度分析
时间复杂度:O(N \times C^2)O(N×C
2
)。其中 NN 为 wordList 的长度,CC 为列表中单词的平均长度。
建图过程中,对于每一个单词,我们需要枚举它连接到的所有虚拟节点,时间复杂度为 O©O©,将这些单词加入到哈希表中,时间复杂度为 O(N \times C)O(N×C),因此总时间复杂度为 O(N \times C)O(N×C)。
双向广度优先搜索的时间复杂度最坏情况下是 O(N \times C)O(N×C)。每一个单词需要拓展出 O©O© 个虚拟节点,因此节点数 O(N \times C)O(N×C)。
空间复杂度:O(N \times C^2)O(N×C
2
)。其中 NN 为 wordList 的长度,CC 为列表中单词的平均长度。哈希表中包含 O(N \times C)O(N×C) 个节点,每个节点占用空间 O©O©,因此总的空间复杂度为 O(N \times C^2)O(N×C
2
)。
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/word-ladder/solution/dan-ci-jie-long-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。