【搜索】A061_LC_單詞接龍 II((bfs / 雙向 bfs) + 回溯)

一、Problem

Given two words (beginWord and endWord), and a dictionary’s word list, find all shortest transformation sequence(s) 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 an empty list 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.
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

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

二、Solution

方法一:bfs + 回溯

  • bfs 構建從 beginWord 變換得到的合法新單詞 newWord 的鄰接表。
  • 因爲是 bfs,所以當找到 newWord 爲 endWord 時,遍歷完當前層得到的路徑就是最短的了,退出 bfs。

Q:爲什麼 bfs 完還需要回溯求出所有路徑?
A:其實也可以在 bfs 的過程中記錄遍歷得到的路徑,但這樣做會比較低效,因爲 bfs 不一定就是可以得到合法路徑的,而無效的路徑中的結點的添加卻是無用的。

Q:爲什麼要遍歷完一層纔將訪問過的結點標記爲 visited?
A:因爲有可能當前層的某個單詞 w1 的下一個單詞 nw1 和當前層的別的單詞 w2 的下一個單詞 nw2 是相同的(nw1 == nw2),而 w1 和 w2 都可以到達 endWord,所以不能提前設置狀態。

class Solution {
    Map<String, Set<String>> e;
    Set<String> st;

    void bfs(String bW, String eW) {
        Queue<String> q = new LinkedList<>();
        Set<String> vis = new HashSet<>();
        q.add(bW);
        vis.add(bW);
        int N = eW.length();
        Set<String> nextLevel = new HashSet<>();

        while (!q.isEmpty()) {
            int sz = q.size();
            boolean found = false;
            for (int i = 0; i < sz; i++) {
                String cW = q.poll();
                char[] cs = cW.toCharArray();
                for (int j = 0; j < N; j++) { //將每一個字符都嘗試變換
                    char oriChar = cs[j];
                    for (char k = 'a'; k <= 'z'; k++) {
                        if (k == oriChar)
                            continue;
                        cs[j] = k;
                        String nW = new String(cs);
                        if (!st.contains(nW) || vis.contains(nW))
                            continue;
                        if (eW.equals(nW))
                            found = true;
                        q.add(nW);
                        e.computeIfAbsent(cW, v -> new HashSet<>()).add(nW);
                        nextLevel.add(nW);
                    }
                    cs[j] = oriChar;
                }
            }
            if (found)
                break;
            vis.addAll(nextLevel);
            nextLevel.clear();
        }
    }
    List<List<String>> ps;
    LinkedList<String> p;
    void dfs(String cur, String eW) {
        if (cur.equals(eW)) {
            ps.add(new LinkedList<>(p));
            return;
        }
        if (!e.containsKey(cur))
        	return;
        for (String to : e.get(cur)) {
            p.add(to);
            dfs(to, eW);
            p.pollLast();
        }
    }
    public List<List<String>> findLadders(String bW, String eW, List<String> wL) {
        ps = new LinkedList<>();
        if (wL == null || wL.size() == 0)
            return ps;
        p = new LinkedList<>();
        e = new HashMap<>();
        st = new HashSet<>(wL);

        bfs(bW, eW);
        // Set<String> nei = e.getOrDefault(bW, new HashSet<String>());
        // for (String to : nei) System.out.print(to);
        p.add(bW);
        dfs(bW, eW);
        return ps;
    }
}

複雜度分析

  • 時間複雜度:O(n×wordLen×(n×2n))O(n × wordLen × (n × 2^n))
  • 空間複雜度:O(ps.size()×n×wordLen)O(ps.size() × n × wordLen)

方法二:雙向 bfs

在這裏插入圖片描述
別人的思路:從前到後和從後到前,同時遍歷,兩個遍歷遇到相同的結點就表明該路徑爲合法路徑。因爲兩個方向的遍歷會有兩個容器保存結點,爲了方便,遍歷時可以設定一個規則:從 size 小的集合開始遍歷,當 size 小的集合變爲更大者的時候,就要交換集合,同時邊的添加方向也需要改變。

class Solution {
    Map<String, Set<String>> e;
    Set<String> st;
    boolean bfs(String bW, String eW) {
        Set<String> b2e = new HashSet<>(), e2b = new HashSet<>(),  vis = new HashSet<>();
        b2e.add(bW); e2b.add(eW);
        vis.add(bW); vis.add(eW);
        boolean found = false, isB2E = true;

        while (!b2e.isEmpty() && !e2b.isEmpty()) {
            if (b2e.size() > e2b.size()) {
                Set<String> t = b2e;
                b2e = e2b;
                e2b = t;
                isB2E = !isB2E;
            }
            Set<String> nextLevel = new HashSet<>();
            for (String cW : b2e) {
                char[] cs = cW.toCharArray();
                for (int j = 0; j < cs.length; j++) {   //將每一個字符都嘗試變換
                    char oriChar = cs[j];
                    for (char k = 'a'; k <= 'z'; k++) {
                        if (k == oriChar)
                            continue;
                        cs[j] = k;
                        String nW = new String(cs);
                        if (!st.contains(nW))
                            continue;
                        if (e2b.contains(nW)) {
                            found = true;
                            addEdge(cW, nW, isB2E);
                        }
                        if (!vis.contains(nW)) {
                            nextLevel.add(nW);
                            addEdge(cW, nW, isB2E);
                        }
                    }
                    cs[j] = oriChar;
                }
            }
            if (found)
                return true;
            b2e = nextLevel;
            vis.addAll(nextLevel);
        }
        return false;	// 注
    }
    void addEdge(String cur, String nex, boolean isB2E) {
        if (!isB2E) {
            String t = cur;
            cur = nex;
            nex = t;
        }
        e.computeIfAbsent(cur, v -> new HashSet<>()).add(nex);
    }
    List<List<String>> ps;
    LinkedList<String> p;
    void dfs(String cur, String eW) {
        if (cur.equals(eW)) {
            ps.add(new LinkedList<>(p));
            return;
        }
        if (!e.containsKey(cur))
            return;
        for (String to : e.get(cur)) {
            p.add(to);
            dfs(to, eW);
            p.pollLast();
        }
    }
    public List<List<String>> findLadders(String bW, String eW, List<String> wL) {
        ps = new LinkedList<>();
        st = new HashSet<>(wL);
        if (wL == null || !st.contains(eW))	// 注
            return ps;
        p = new LinkedList<>();
        e = new HashMap<>();
        boolean found = bfs(bW, eW);
        if (!found)
            return ps;
        p.add(bW);
        dfs(bW, eW);
        return ps;
    }
}

複雜度分析

  • 時間複雜度:O(n×wordLen×n×2n)O(n × wordLen × n × 2^n),n 爲單詞數
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章