Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation sequence from beginWordto endWord, such that:
- Only one letter can be changed at a time
- Each intermediate word must exist in the word list
For example,
Given:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog"
,
return its length 5
.
Note:
- Return 0 if there is no such transformation sequence.
- All words have the same length.
- All words contain only lowercase alphabetic characters.
思路:
LeetCode中为数不多的考图的难题。尽管题目看上去像字符串匹配题,但从“shortest transformation sequence from start to end”还是能透露出一点图论中最短路径题的味道。如何转化?
1. 将每个单词看成图的一个节点。
2. 当单词s1改变一个字符可以变成存在于字典的单词s2时,则s1与s2之间有连接。
3. 给定s1和s2,问题I转化成了求在图中从s1->s2的最短路径长度。而问题II转化为了求所有s1->s2的最短路径。
无论是求最短路径长度还是求所有最短路径,都是用BFS。在BFS中有三个关键步骤需要实现:
1. 如何找到与当前节点相邻的所有节点。
这里可以有两个策略:
(1) 遍历整个字典,将其中每个单词与当前单词比较,判断是否只差一个字符。复杂度为:n*w,n为字典中的单词数量,w为单词长度。
(2) 遍历当前单词的每个字符x,将其改变成a~z中除x外的任意一个,形成一个新的单词,在字典中判断是否存在。复杂度为:26*w,w为单词长度。
这里可以和面试官讨论两种策略的取舍。对于通常的英语单词来说,长度大多小于100,而字典中的单词数则往往是成千上万,所以策略2相对较优。
2. 如何标记一个节点已经被访问过,以避免重复访问。
可以将访问过的单词从字典中删除。
3. 一旦BFS找到目标单词,如何backtracking找回路径?
解法一:
public class Solution { public int ladderLength(String beginWord, String endWord, Set<String> wordList) { if (wordList.size() == 0) return 0; Queue<String[]> queue = new LinkedList<String[]>(); wordList.add(endWord); String[] pair = new String[2]; int len = 1; pair[0] = beginWord; pair[1] = Integer.toString(len); //int to String queue.add(pair); while(!queue.isEmpty()) { String[] top = queue.poll(); String s = top[0]; len = Integer.parseInt(top[1]); //String to int if(s.equals(endWord))return len; ArrayList<String> neighbors = findNeighbors(s,wordList); for(int i=0;i<neighbors.size();i++) { String[] temp = new String[2]; temp[0] = neighbors.get(i); temp[1] = Integer.toString(len+1); queue.add(temp); } } return 0; } public ArrayList<String> findNeighbors(String s, Set<String> wordList) { ArrayList<String> res = new ArrayList<String>(); for(int i=0;i<s.length();i++) { char[] sChar = s.toCharArray(); for(char c = 'a';c<'z';c++) { sChar[i]=c; String newWord = new String(sChar); if(wordList.contains(newWord)) { res.add(newWord); wordList.remove(newWord);//删除掉遍历过的单词 } } } return res; } };
解法二:用一个空的String名为tag来处理有多个可以走向的String的问题。
public class Solution { public int ladderLength(String start, String end, Set<String> dict) { if (dict.size() == 0) return 0; LinkedList<String> queue = new LinkedList<String>(); String tag = new String(); queue.add(start); queue.add(tag); int len = 1; while(queue.size() > 1){ String top = queue.pop(); if(top == tag){ len++; queue.add(tag); continue; } if(top.equals(end)){ return len; } for(int i=0; i<top.length(); i++){ char[] currCharArr = top.toCharArray(); for(char c='a'; c<='z'; c++){ currCharArr[i] = c; String newWord = new String(currCharArr); if(dict.contains(newWord)){ queue.add(newWord); dict.remove(newWord);//删除掉遍历过的单词 } } } } return 0; } }
reference: http://bangbingsyb.blogspot.com/2014/11/leetcode-word-ladder-i-ii.html