Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation sequence from beginWord to endWord, such that:
- Only one letter can be changed at a time.
- Each transformed word must exist in the word list. Note that beginWord is not a transformed word.
Note:
- Return 0 if there is no such transformation sequence.
- All words have the same length.
- All words contain only lowercase alphabetic characters.
- You may assume no duplicates in the word list.
- You may assume beginWord and endWord are non-empty and are not the same.
Example 1:
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.
Example 2:
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.
這是很典型的單源頭廣度優先搜索最短路徑的問題,只要想到這里,結合圖的知識就很好做了。怎么構建圖呢?首先圖的頂點顯而易見,就是每一個單詞。那么頂點之間的邊呢?就是如果頂點的單詞互相只差一個字母,頂點間就會形成邊。用第一個例子繪制的圖如下:
那么根據廣度優先搜索的步驟,構建一個隊列,之后先把源頂點加入隊列,以隊列是否為空作為條件開始循環:如果隊列不為空,逐個取出隊列中的頂點,並把它們標記為“已訪問”。對於每個取出的頂點,依次把它未訪問的頂點加入隊列,直到找到目標頂點或者所有頂點都訪問完畢。因為此題只是求最短路徑的長度,而不是路徑上都有哪些頂點,所以只需要存儲每訪問一個頂點時該頂點處在路徑的位置即可。這里我用了HashMap<String, Integer>。如果是需要存儲最短路徑本身,那么需要建立數據結構依次存儲每個頂點的前驅,並在最后追溯前驅獲得路徑上的所有頂點。
這里我用一個HashSet替換掉題目中存儲單詞的List數據結構,只是為了搜索的時候能夠快一點。至於如何獲取某個節點的鄰接節點,即和它只差一個字母的單詞,這里用了最暴力的辦法,就是把頂點單詞的每一位換成其他的25個字母,看看它在不在題目提供的字典里。詳細的標注都已經標注在代碼中,見下。
Java
class Solution { public int ladderLength(String beginWord, String endWord, List<String> wordList) { HashSet<String> wordSet = new HashSet<>(wordList); //替換掉題目中List結構,加速查找 if (!wordSet.contains(endWord)) return 0; //如果目標頂點不在圖中,直接返回0 HashMap<String, Integer> map = new HashMap<>(); //用來存儲已訪問的節點,並存儲其在路徑上的位置,相當於BFS算法中的isVisted功能 Queue<String> q = new LinkedList<>(); //構建隊列,實現廣度優先遍歷 q.add(beginWord); //加入源頂點 map.put(beginWord, 1); //添加源頂點為“已訪問”,並記錄它在路徑的位置 while (!q.isEmpty()) { //開始遍歷隊列中的頂點 String word = q.poll(); //記錄現在正在處理的頂點 int level = map.get(word); //記錄現在路徑的長度 for (int i = 0; i < word.length(); i++) { char[] wordLetter = word.toCharArray(); for (char j = 'a'; j <= 'z'; j++) { if (wordLetter[i] == j) continue; wordLetter[i] = j; //對於每一位字母,分別替換成另外25個字母 String check = new String(wordLetter); if (check.equals(endWord)) return map.get(word) + 1; //如果已經抵達目標節點,返回當前路徑長度+1 if (wordSet.contains(check) && !map.containsKey(check)) { //如果字典中存在鄰接節點,且這個鄰接節點還未被訪問 map.put(check, level + 1); //標記這個鄰接節點為已訪問,記錄其在路徑上的位置 q.add(check); //加入隊列,以供廣度搜索 } } } } return 0; } }