【LeetCode】127. 單詞接龍

白露塞納發表於2020-11-06

一、題目

給定兩個單詞(beginWord 和 endWord)和一個字典,找到從 beginWord 到 endWord 的最短轉換序列的長度。轉換需遵循如下規則:

  1. 每次轉換隻能改變一個字母。
  2. 轉換過程中的中間單詞必須是字典中的單詞。

說明:

  • 如果不存在這樣的轉換序列,返回 0。
  • 所有單詞具有相同的長度。
  • 所有單詞只由小寫字母組成。
  • 字典中不存在重複的單詞。
  • 你可以假設 beginWord 和 endWord 是非空的,且二者不相同。

示例 1:

輸入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

輸出: 5

解釋: 一個最短轉換序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的長度 5

示例 2:

輸入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

輸出: 0

解釋: endWord "cog" 不在字典中,所以無法進行轉換。

二、解決

1、BFS

思路: (主要解釋來自參考2)

先附一下二叉樹的BFS/層序遍歷模板。

public List<List<Integer>> BFS(TreeNode root) {

	List<List<Integer>> allResults = new ArrayList<>();
    if (root == null)  return allResults;
    
    Queue<TreeNode> nodes = new LinkedList<>();
    nodes.add(root);
    while (!nodes.isEmpty()) {
        int levelSize = nodes.size();
        List<Integer> results = new ArrayList<>();
        for (int i = 0; i < levelSize; i++) {
            TreeNode currNode = nodes.poll();
            results.add(currNode.val);
            if (currNode.left != null)  nodes.add(currNode.left);
            if (currNode.right != null)  nodes.add(currNode.right);
        }
        allResults.add(results);
    }
    return allResults;
}

過程:

  • 無向圖中兩個頂點之間的最短路徑的長度,可以通過廣度優先遍歷得到;

  • 為什麼 BFS 得到的路徑最短?

    可以把起點和終點所在的路徑拉直來看,兩點之間線段最短。(想象層層水波往外輻射)

  • 可能過程如下圖:

由於程式碼是從a-z迴圈替換判斷,所以實際替換過程走上半段,即 "hit" -> "hot" -> "dot" -> "dog" -> "cog"

BFS

程式碼:

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Queue<String> q = new LinkedList<>();
        q.offer(beginWord);
        HashSet<String> wordSet = new HashSet<>(wordList);
        wordSet.remove(beginWord); // the same as marking visited
        int step = 1;
        while (!q.isEmpty()) {
            int size = q.size(); 
            while (size-- > 0) {
                String str = q.poll();
                if (str.equals(endWord)) return step; // found shortest transformation path
                for (int i = 0; i < str.length(); i++) {
                    char[] chars = str.toCharArray();
                    for (char c = 'a'; c <= 'z'; c++) { // try to change 1 character of `str`
                        chars[i] = c;
                        String newStr = new String(chars);
                        if (wordSet.contains(newStr)) {
                            q.offer(newStr);
                            wordSet.remove(newStr); // the same as marking visited
                        }
                    }
                }
            }
            step++;
        }
        return 0; // no such transformation sequence.
    }
}

時間複雜度:
空間複雜度:

2、Two-ended BFS

思路:

  1. 已知目標頂點(即終點),可從分別起點和終點執行BFS,直到遍歷有交集,這種方式搜尋的單詞數量會更小點。而且每次可以從單詞數量少的集合開始擴散,以提升速度。

  2. 這裡 start(visited) 和 end(visited) 交替使用,等價於單向 BFS 裡使用佇列,每次擴散都要加到總的 visited 裡。
    雙向DFS

程式碼:

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Set<String> dict = new HashSet<>(wordList);
        Set<String> start = new HashSet<>();
        Set<String> end = new HashSet<>();
        Set<String> visited = new HashSet<>();
        // 1. initialize the beginWord
        start.add(beginWord);
        // 2. all transformed words must be in dict (including endWord)
        if (dict.contains(endWord)) end.add(endWord); 
        // 3. BFS 
        for (int len = 2; !start.isEmpty(); len++) {
            Set<String> current = new HashSet<>();
            
            for (String w : start) {
                for (int j = 0; j < w.length(); j++) {
                    char[] ch = w.toCharArray();
                    
                    for (char c = 'a'; c <= 'z'; c++) {
                        if (c == w.charAt(j)) continue; // beginWord and endWord not the same, so bypass itself
                        ch[j] = c;
                        String nb = String.valueOf(ch);
                        if (end.contains(nb)) return len; // meet from two ends
                        if (dict.contains(nb) && visited.add(nb)) current.add(nb); // not meet yet, visited is safe to use
                    }
                }
            }
            start = (current.size() < end.size()) ? current : end; // switch to small one to traverse from other end
            end = (start == current) ? end : current;
        }
        return 0;
    }
}

時間複雜度: O ( 26 ∗ M ∗ N ) O(26*M*N) O(26MN),M是單詞長度,N是輸入字典表長度。
空間複雜度: O ( M ∗ N ) O(M*N) O(MN)

三、參考

1、單詞接龍
2、廣度優先遍歷、雙向廣度優先遍歷(Java)
3、[Java] BFS Solution - Clean code
4、Simple Java BFS solution with explanation
5、Two-end BFS in Java 31ms.

相關文章