怎么说呢,这个题目我并不会做,或者说想了半天发现根本不是那么回事,就只是无脑想,无脑循环,没有想到应该用图这种数据结构去解决问题。所以直接看了下答案,到底是怎么做的。接下来这一段就是这个题目的思路。
首先我们把每个单词想象成一个节点,而每个节点能否与其他节点相连就是看是不是两个单词之间只有一个字母不同。于是从一个节出发,就可以将列表中所有的节点构成一个图。举个例子,就拿题目的第二个测试用例中的单词列表"[hot","dot","dog","lot","log"]
,我们从单词hit
出发,画出的图如下所示:
于是从一个单词最后转为另一个单词最少步骤数也就变为了从一个节点出发到另一个节点的最短路径,这个问题应该使用**广度优先遍历(BFS)**方法。
思路有了,接下来就是写程序了,我们接触到的图结构基本上就是邻接矩阵、邻接表、有向图 十字链表、无向图 邻接多重表等,那今天这个问题用什么来解决比较好呢?我们都不用,我们没有使用传统真正的图结构来存储这个问题,而是基于以下思路:如果两个单词只在某一个位置上不同,那么就把他们放在一个列表里,表示这些节点相互之间都是连通的。而如何表示他们在哪个位置是不同的呢,我们针对单词每个位置都用字符"*"
来替换,只要都一个单词和另一个同时都能转换成一个替换字符串,证明它们是符合只有一位不同的条件,例如
Dog---->D*g<----Dig
,于是我们可以以D*g
为键,[Dog, Dig]
为值放到哈希表中。
而广度优先遍历则使用队列来进行,队列中的元素应该存当前的节点和当前节点所处位置(层),最后直到遇到endWord为止。代码如下:
import javafx.util.Pair;
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// 所有字符的长度一定是相等的
int L = beginWord.length();
// 把有相同模式(单词中某个字符替换为*后)所有能转换为此模式的单词放在一起
HashMap<String, ArrayList<String>> allComboDict = new HashMap<String, ArrayList<String>>();
wordList.forEach(
word -> {
for (int i = 0; i < L; i++) {
// 构建模式单词
String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);
// 获得此模式单词对应的单词列表,即能转换为此模式的所有单词
ArrayList<String> transformations =
allComboDict.getOrDefault(newWord, new ArrayList<String>());
// 把当前单词加到此列表中,并放回到map
transformations.add(word);
allComboDict.put(newWord, transformations);
}
});
// 遍历队列,队列应该放当前的单词(节点)和对应的层数,开始节点在第一层
Queue<Pair<String, Integer>> Q = new LinkedList<Pair<String, Integer>>();
Q.add(new Pair(beginWord, 1));
// 也要构建一个访问标志哈希表,不然会无限循环下去
HashMap<String, Boolean> visited = new HashMap<String, Boolean>();
visited.put(beginWord, true);
while (!Q.isEmpty()) {
Pair<String, Integer> node = Q.remove();
String word = node.getKey();
int level = node.getValue();
for (int i = 0; i < L; i++) {
// 构造当前所在节点的模式字符串
String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);
// 根据当前节点模式字符串查找所有未访问的相邻节点,并加入到队列中
for (String adjacentWord : allComboDict.getOrDefault(newWord, new ArrayList<String>())) {
// 如果有相邻节点是终点,那就是最短的,直接返回当前节点的层数加1
if (adjacentWord.equals(endWord)) {
return level + 1;
}
// 否则将所有的未访问的相邻节点入队,且入队是层数是加1的
if (!visited.containsKey(adjacentWord)) {
visited.put(adjacentWord, true);
Q.add(new Pair(adjacentWord, level + 1));
}
}
}
}
return 0;
}
}