Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
前言
力扣第126题 单词接龙 II 如下所示:
按字典 wordList 完成从单词 beginWord 到单词 endWord 转化,一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk 这样的单词序列,并满足:
- 每对相邻的单词之间仅有单个字母不同。
- 转换过程中的每个单词
si(1 <= i <= k)必须是字典wordList中的单词。注意,beginWord不必是字典wordList中的单词。 sk == endWord
给你两个单词 beginWord 和 endWord ,以及一个字典 wordList 。请你找出并返回所有从 beginWord 到 endWord 的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。每个序列都应该以单词列表 [beginWord, s1, s2, ..., sk] 的形式返回。
示例 1:
输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
解释: 存在 2 种最短的转换序列:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"
一、思路
题目很长,也不是很好理解,需要多读几遍题目。简单翻译一下题目就是:用 endWord 中的字符替换 beginWord 中的字符,每次只替换一个字符(注意下一次替换是在 beginWord' 的基础上进行的)。直到替换的字符 beginWord' 都要在集合 wordList 中出现。
我们再把题目中的重要信息列举一下(注意看提示!):
beginWord.len == endWord.len两字符串长度相等beginWord != endWord两者内容不一致。()- 所有字符均有小写字母构成
wordList中所有单词都不相同(如有重复元素肯定找不到序列)
图解算法
以下的思路根据题目中的示例一作为具体的例子分析
我们既然知道了结果集合 wordList,那么就可以推断出从 beginWord -> endWord 的路径上会有哪些节点,以及各个节点间的关联。例如 hot 可以通过字符的替换变为 dot 或 lot。具体的关联图如下所示:
为了更好的看清整个过程,我将起始节点 beginWord 也加进来了
我们可以看到节点的关联中还存在双向关联的情况,像这种双向的关联我们可以直接删除掉,因为最终的结果中要求的是 最短路径,如果选择了双向关联的两个节点会导致路径的长度边长。
然后我们根据图中的关联关系很容以就能找到最短的路径了,两条最短的路径如下图所示:
第一条
第二条
思路有了实现起来也不难了,主要分为以下两个步骤:
- 使用
广度遍历生成一个有向图,明确各个节点间的关联关系 - 利用关联关系,
递归找到最短的路径
二、实现
实现代码
在构建图的时候,可以从开始字符串出发,从 a ~ z 逐个替换字符,直到在 wordList 中出现
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
List<List<String>> res = new ArrayList<>();
Set<String> dict = new HashSet<>(wordList); // 字典
if (!dict.contains(endWord)) {
return res;
}
dict.remove(beginWord); // 要交换字符,字典中不能有起始字符串 beginWord
Map<String, Integer> steps = new HashMap<>();
steps.put(beginWord, 0);
Map<String, Set<String>> from = new HashMap<>();
boolean found = bfs(beginWord, endWord, dict, steps, from);
if (found) {
Deque<String> path = new ArrayDeque<>();
path.add(endWord);
dfs(from, path, beginWord, endWord, res);
}
return res;
}
/**
* 广度遍历
*/
private boolean bfs(String beginWord, String endWord, Set<String> dict, Map<String, Integer> steps, Map<String, Set<String>> from) {
int wordLen = beginWord.length();
int step = 0;
boolean found = false; // 是否找到了结果字符串
Queue<String> queue = new LinkedList<>();
queue.add(beginWord);
while (!queue.isEmpty()) {
step++;
int size = queue.size();
for (int i = 0; i < size; i++) {
String currWord = queue.remove();
char[] charArray = currWord.toCharArray();
for (int j = 0; j < wordLen; j++) {
char origin = charArray[j];
// 尝试找到下一个替换后的字符串
for (char c = 'a'; c <= 'z'; c++) {
charArray[j] = c;
String nextWord = String.valueOf(charArray);
if (steps.containsKey(nextWord) && steps.get(nextWord) == step) {
from.get(nextWord).add(currWord);
}
if (!dict.contains(nextWord)) {
continue;
}
dict.remove(nextWord);
queue.add(nextWord);
if (!from.containsKey(nextWord)){
from.put(nextWord, new HashSet<>());
}
from.get(nextWord).add(currWord);
steps.put(nextWord, step);
if (nextWord.equals(endWord)) {
found = true;
}
}
charArray[j] = origin;
}
}
if (found) {
break;
}
}
return found;
}
/**
* 递归
*/
private void dfs(Map<String, Set<String>> from, Deque<String> path, String beginWord, String cur, List<List<String>> res) {
if (cur.equals(beginWord)) {
res.add(new ArrayList<>(path));
return;
}
for (String precursor : from.get(cur)) {
path.addFirst(precursor);
dfs(from, path, beginWord, precursor, res);
path.removeFirst();
}
}
测试代码
public static void main(String[] args) {
String being = "hit";
String end = "cog";
List<String> list = Arrays.asList("hot","dot","dog","lot","log","cog");
new Number126().findLadders(being, end, list);
}
结果
三、总结
感谢看到最后,非常荣幸能够帮助到你~♥
如果你觉得我写的还不错的话,不妨给我点个赞吧!如有疑问,也可评论区见~