原题链接:https://leetcode-cn.com/problems/word-ladder/description/
题目描述:
知识点:队列,图的广度优先遍历
基本思路:
(1)新建一个辅助的类LevelWord,类包含两个属性,代表word的String类型的变量,以及代表word层级的int型变量level。在构造函数可以给这两个变量赋值。
(2)新建一个辅助函数,用以判断两个字符串之间是否能够转换。遍历两个字符串,根据题意,如果不相等的字符的数量大于1,则无法相互转换。只有不相等的字符的数量等于1时,这两个字符串才能够相互转换。
(3)在ladderLength()函数里,我们首先判断endWord字符串是否在所给的wordList中,如果endWord字符串不在所给的wordList中,说明最短路径不存在,无法进行转换,依据题意,我们直接返回0。
(4)为了实现的方便,如果beginWord不在wordList中,我们将beginWord添加进wordList。
(5)新建一个boolean类型的二维数组nextWords用来表示wordList中第i个位置和第j个位置的字符串能否相互转换,如果能相互转换,则nextWords[i][j] = true。我们通过遍历nextWord两次来设置nextWords的值。由于两个点之间的转换是相互的,所以本题本质上是一个无向图。我们在遍历nextWord两次的时候可以做一次优化,同时设置nextWords[i][j]和nextWords[j][i]的值,因此我们只需遍历矩阵的上三角或者下三角即可。
(6)新建一个HashMap类型的数据visited,其键类型为String,其值类型为Boolean,用来记录wordList中对应的字符串是否已经被访问过。初始化visited时,将wordList中的所有元素都放入visited中,每个元素对应的值设为false,表示所有节点都未被访问。
(7)新建一个队列queue,里面存放的是LevelWord类型的数据,初始时,新建一个word值为beginWord,level值为0的LevelWord类型的变量入队,并将visited中beginWord对应的值设为true。
(8)写一个循环,只要队列queue不为空就一直进行该循环,在循环体里我们做以下几件事。
a.取出队首元素temp,判断temp的word属性是否和endWord相等,注意判断两个字符串变量相等我们需要用equals()方法而不能用==号。如果相等,则直接返回temp.level + 1,为什么要+1呢?因为我们在(7)中第一个入队的元素的level值为0。
b.新建一个List<String>类型的变量nextWord,用来记录可以与temp.word字符串相互转换的所有字符串。根据我们在(5)中得到的二维数组nextWords来设置nextWord中包含哪些元素。
c.遍历nextWord中的元素,如果该元素还没有被访问,即该元素的visited键对应的值为false,则新建一个word值为该元素,level值为temp.level + 1的LevelWord类型的变量入队,并将相应的visited键对应的值设为true。
(8)while循环结束之后,虽然我们知道这个函数在while循环里一定会直接返回,不会进行while循环之后的所有语句,但编译器并不知道这一点。如果我们不在while循环后加一个return语句,编译器是会报错的,因此在while循环后我们return一个任意的int型数据,这里我return的是0。
JAVA代码:
class LevelWord {
String word;
int level;
public LevelWord(String word, int level) {
this.word = word;
this.level = level;
}
}
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
int end = wordList.indexOf(endWord);
if(end == -1) {
return 0;
}
if(!wordList.contains(beginWord)) {
wordList.add(beginWord);
}
boolean[][] nextWords = new boolean[wordList.size()][wordList.size()];
HashMap<String, Boolean> visited = new HashMap<>();
for (int i = 0; i < wordList.size(); i++) {
for (int j = 0; j < i; j++) {
if(hasPath(wordList.get(i).toCharArray(), wordList.get(j).toCharArray())) {
nextWords[i][j] = nextWords[j][i] = true;
}
}
visited.put(wordList.get(i), false);
}
Queue<LevelWord> queue = new LinkedList<>();
queue.add(new LevelWord(beginWord, 0));
visited.put(beginWord, true);
while(!queue.isEmpty()) {
LevelWord temp = queue.poll();
if(temp.word.equals(endWord)) {
return temp.level + 1;
}
List<String> nextWord = new ArrayList<>();
int n = wordList.indexOf(temp.word);
for (int i = 0; i < nextWords[n].length; i++) {
if(nextWords[n][i]) {
nextWord.add(wordList.get(i));
}
}
for (int i = 0; i < nextWord.size(); i++) {
String nextTemp = nextWord.get(i);
if(!visited.get(nextTemp)) {
queue.add(new LevelWord(nextTemp, temp.level + 1));
visited.put(nextTemp, true);
}
}
}
return 0;
}
private boolean hasPath(char[] arr1, char[] arr2) {
int diff = 0;
for (int i = 0; i < arr1.length; i++) {
if(arr1[i] != arr2[i]) {
diff++;
}
}
if(diff == 1) {
return true;
}
return false;
}
复杂度分析:
时间复杂度:
(1)判断wordList各个字符串间是否有路径的复杂度是O(m * n ^ 2),n表示wordList中的元素个数,m表示wordList中字符串的长度。
(2)在队列中进行的操作的复杂度是O(n * x),x为能与每个字符串相互转换的字符串数量,是一个未知值。
总的时间复杂度为O(m * n ^ 2)。
空间复杂度:
(1)需要存储一个邻接矩阵用以判断wordList中各个字符串间是否有路径,该邻接矩阵的空间为O(n * n)。
(2)其他还有一些比如存放是否已访问visited变量等都是O(n)级别的复杂度。
总的空间复杂度为O(n * n)。