1. 程式人生 > >LeetCode-126.單詞接龍II(相關話題:Dijkstra演算法+深度優先)

LeetCode-126.單詞接龍II(相關話題:Dijkstra演算法+深度優先)

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

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

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

示例 1:

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

輸出:
[
  ["hit","hot","dot","dog","cog"],
  ["hit","hot","lot","log","cog"]
]

示例 2:

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

輸出: []

解釋: endWord "cog" 不在字典中,所以不存在符合要求的轉換序列。

Dijkstra演算法:演算法執行過程中,維持一個數組(最小優先佇列),陣列中記錄起點到每個節點的最短路徑

在這道題中(相當於每條邊權值均為1的有向圖),用Map記錄單詞字典中每個單詞至beginWord的最短路徑(即beginWord最少需要經過多少次才能轉換為字典中的每個單詞)

同時,在Dijkstra演算法計算最短路徑的過程中,用Map> relation記錄單詞字典中每個單詞的父節點(前一單詞)

,處理規則如下:如上圖中,由結點2更新與結點2相鄰的結點(即結點3),由於1+2=3小於4(之前到結點3的最短路徑),因此將relation中結點3的父節點list清空再將結點2放進去;若結點1到結點3的邊的權值為3,由結點2更新結點3時,由於1+2=3,與之前到結點3的最短路徑相等,因此可直接將結點1新增至relation中結點3的父節點list中(其中已經有結點2);若relation中不存在key-結點3(即結點1不與結點3直接相連),直接將結點3和包含結點2的list放入relation中

計算完單詞字典中每個單詞的父節點(前一單詞)後,用深度優先遍歷,從endWord開始倒推出最短轉換路徑

注意:在Dijkstra演算法計算過程中,由於需要每次從未訪問的結點中求出最短路徑最小的結點,以更新周圍直接相連的結點,需要最小優先佇列來每次獲取當前路徑最短的結點,但這個題中,由於每條邊的權值均為1,因此可按照訪問到的順序將單詞放入佇列中,按照訪問順序取出的單詞,一定是當前未訪問結點的最短路徑,可提高效率。

Java程式碼:

class Solution {
    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        /**
         * Dijkstra演算法
         * 1.用Map<String, Integer>維護每個元素到起點的長度,初始為∞
         * 2.用Map<String, List<String>>維護每個節點最短路徑的前一節點
         */
        Queue<String> queue = new java.util.concurrent.LinkedBlockingQueue<>();
        Map<String, Integer> dis = new HashMap<>();
        Map<String, List<String>> relation = new HashMap<>();

        dis.put(beginWord, 0);
        queue.add(beginWord);
        while(!queue.isEmpty()){
            // 獲取未訪問節點中,路徑最短的節點,所有邊的權重為1
            String str = queue.poll();
            int minPath = dis.get(str);

            // 更新與未訪問節點中路徑最短節點相鄰節點的最短路徑,同時記錄節點的最短路徑前一節點
            for(String key: wordList){

                // 判斷兩個節點是否相鄰
                int k = 0;
                for(int i = 0; i < key.length(); i++){
                    if(str.charAt(i) != key.charAt(i))
                        k++;

                    if(1 < k)
                        break;
                }
                // 相鄰節點,更新最短路徑,同時記錄節點的最短路徑前一節點
                if(1 == k){
                    if(null == relation.get(key) || null == dis.get(key) || minPath+1 < dis.get(key)){
                        List<String> re = new ArrayList<>();
                        re.add(str);
                        relation.put(key, re);
                    } else {
                        if(minPath+1 == dis.get(key))
                            relation.get(key).add(str);

                    }
                    if(!dis.containsKey(key))
                        queue.add(key);
                    dis.put(key, Math.min(minPath+1, null == dis.get(key) ? Integer.MAX_VALUE : dis.get(key)));
                }
            }

            // 剔除已訪問過節點
            dis.remove(str);
            wordList.remove(str);
        }

        List<List<String>> res = new LinkedList<>();
        LinkedList<String> tmp = new LinkedList<>();
        tmp.addFirst(endWord);
        dfs(endWord, tmp, relation, res, beginWord);

        return res;
    }
    private void dfs(String cur, LinkedList<String> tmp, Map<String, List<String>> relation, List<List<String>> res, String beginWord){
        if(cur.equals(beginWord)){
            res.add(new LinkedList<>(tmp));
            return;
        }
        if(!relation.containsKey(cur))
            return;

        List<String> pre = relation.get(cur);
        for(int i = 0; i < pre.size(); i++){
            cur = pre.get(i);
            tmp.addFirst(cur);
            dfs(cur, tmp, relation, res, beginWord);
            tmp.removeFirst();
        }
    }
}