算法设计与分析第五周作业——Word Ladder
上周找了一道深度搜索优先搜索的算法题来做,于是这周就选了一道广度优先搜索算法题来试试手。
本周所选题目:原题目链接
题目详情
题目大意:给出一个字符串(单词)集合和两个字符串,一个为开始字符串,一个为结束字符串,每次通过改变一个字符使得从开始字符串依次转变为字符串集合里的字符串,直到转化为结束字符串,求所用到的最少的步数。
注:如果不存在这样一条路径,则返回0;
所有涉及到的字符串都是一样大小的,且所有字母都为小写;
字符串集合中无重复的字符串,开始字符串和结束字符串非空。
样例说明:Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
题目分析和算法设计
很显然,题目需要我们通过对每个单词进行广搜以求得最短的路径。可以构建这样的模型:以每个字符串为顶点,如果字符串集合中存在一个只需要当前字符串修改一个字符就能达到的字符串,那么这两个字符串之间就存在有一条边,可以理解为边的值为1。
根据已知条件,我们可知知道应该使用的数据结构为:set(无重复字符串):字符串集合,用来未访问的存储字符串;
queue:用来存储当前访问的字符串;
其中,queue使用键值对来进行对步数的记录,每一个值由(当前字符串,步数)的键值对组成。
具体算法:依次对当前字符串的各个字符进行变换,如果变换之后得到的字符串在字符串集合中,那么说明是可以进行转换的,此时递增步数,并把该字符串放入到队列中,同时从字符串集合中移除,直至转换到结束字符串,返回当前的步数,即队列值的数值。
代码详情class Solution {
public:
int ladderLength(string beginWord, string endWord, vector& wordVec) {
set wordList;
for (auto word : wordVec) {
wordList.insert(word);
}
if (wordList.find(endWord) == wordList.end()) return 0;
queue> que;
que.push(make_pair(beginWord, 1));
while(!que.empty()) {
auto val = que.front();
que.pop();
if(val.first == endWord) return val.second;
for(unsigned i = 0; i < val.first.size(); i++) {
string str = val.first;
for(char j = 'a'; j <= 'z'; j++) {
str[i] = j;
if(wordList.find(str) != wordList.end()) {
que.push(make_pair(str, val.second+1));
wordList.erase(str);
}
}
}
}
return 0;
}
};
上面的题目主要考察简单的广度优先搜索算法,是比较简单的。同时LeetCode上有一道Word Ladder II,虽然题目类似,Word Ladder II要求把具体的所有最短的路径找出来作为返回值。下面我们来分析一下该题:
该题目大概思路跟Word Ladder是类似的,但是在细节方面是有更多需要考虑的。需要求字符串集合的集合,即具体的转换路径的集合,需要以下的数据结构和变量:queue> paths:路径集paths,用以保存所有路径;
unordered_set dict:存未访问过的字符串,初始化为输入的字符串集;
unordered_set words:记录已经循环过的路径中的词;
level:整型变量,记录循环中当前路径的长度;
minLevel:整型变量,记录最短路径的长度;
算法过程:把起始路径加入路径集paths,当paths非空时进行循环,取队列头路径,把路径的最后一个达到的字符串拿出来进行广搜,即对每个字符,都从‘a’-‘z’进行转换,如果dict中有该中间转换得到的字符串,说明满足条件可以进行路径的记录,此时判断该字符串是否为结束字符串,如果是,则找到一条路径,否则把得到的新路径加入paths中,继续循环。需要注意的是如果paths里的路径的长度大于level,说明字典中的有些词已经存入路径了,如果在路径中重复出现,则肯定不是最短路径,所以我们需要在字典中将这些词删去,然后将words清空。
代码详情:class Solution {
public:
vector> findLadders(string beginWord, string endWord, vector& wordList) {
vector> res;
unordered_set dict(wordList.begin(), wordList.end());
vector p{beginWord};
queue> paths;
paths.push(p);
int level = 1, minLevel = INT_MAX;
unordered_set words;
while (!paths.empty()) {
auto t = paths.front();
paths.pop();
if (t.size() > level) {
for (string w : words) dict.erase(w);
words.clear();
level = t.size();
if (level > minLevel) break;
}
string last = t.back();
for (int i = 0; i < last.size(); ++i) {
string newLast = last;
for (char ch = 'a'; ch <= 'z'; ++ch) {
newLast[i] = ch;
if (!dict.count(newLast)) continue;
words.insert(newLast);
vector nextPath = t;
nextPath.push_back(newLast);
if (newLast == endWord) {
res.push_back(nextPath);
minLevel = level;
} else paths.push(nextPath);
}
}
}
return res;
}
};
谢谢阅读。
参考资料: