1. 程式人生 > 其它 >140. Word Break II

140. Word Break II

技術標籤:DFSDynamic Programmingleetcode

Lc-140
categories: [LeetCode]
tags: [Dynamic Programming, DFS, hard]

140. Word Break II

題目大意:

給定一個非空字串和一個字典包含一個非空的 list of non-empty words
在字串中加上空格使字串變成一個句子,使用空格分開的每個單詞都能在字典中找到
返回符合條件的所有可能的組合

注意:
同一個單詞在字典中可以被使用多次,並且字典中沒有重複的單詞

解題思路:

首先這道題是一道DP和DFS結合的題,我們將字典中的所有單詞放入一個HashSet中保證每一個單詞都是不同的
同時我們建立一個boolean型別且長度為s.length()+1的陣列dp表示
長度為i的字串s的字首可以拆分成字典中的單詞
dp[0]為true,因為代表長度為0的字串
接下來我們利用兩個 for 迴圈去遍歷不同長度的字首字串是否可以在hashSet中找到
如果字首字串substring(left,right)可以在HashSet中找到,且left之前的字串也為true, 
則dp[right]為true,並break,因為無需判斷其他的了

接下來我們初始化一個list<string> ans儲存最終結果
如果dp[len]為true表示長度為len的字串可以再字典中找到對應的單詞
建立一個path去儲存字典中能組成的單詞, 然後用dfs進行搜尋

在dfs搜尋中, 如果當前字串長度len為0,則將現有的path放入到ans當中,
並用String.join(" ", path)將path用空格分開
如果長度不為空, 我們用for loop從最後一個字母開始迴圈,
用substring(i, len)取不同長度的字串作為字尾,
如果當前所取字尾在wordSet中,並且dp[i]為true,說明i之前的字串也可以在wordSet中找到,
我們將此時的字尾字串加在path中,然後進行dfs,此時dfs中的len 為 i
然後我們再用removeFirst將之前加進去的字尾刪掉
這樣以當前長度為字尾的字串和剩下的長度的字串就組成一個sentence的組合
然後進行下一個i-- 的for 迴圈直到i=0

注意:

class Gfg1 { 
    public static void main(String args[]) { 
        // delimiter is "<" and elements are "Four", "Five", "Six", "Seven" 
        String gfg1 = String.join(" < ", "Four", "Five", "Six", "Seven"); 

        System.out.println(gfg1); 
    } 
} 


// Output: Four < Five < Six < Seven

複雜度:

Time Coplexity: 
Space Complexity:

Code示例:

class Solution {
 public List<String> wordBreak(String s, List<String> wordDict) {
        // 為了快速判斷一個單詞是否在單詞集合中,需要將它們加入雜湊表
        Set<String> wordSet = new HashSet<>(wordDict);
        int len = s.length();

        // 第 1 步:動態規劃計算是否有解
        // dp[i] 表示「長度」為 i 的 s 字首子串可以拆分成 wordDict 中的單詞
        // 長度包括 0 ,因此狀態陣列的長度為 len + 1
        boolean[] dp = new boolean[len + 1];
        // 0 這個值需要被後面的狀態值參考,如果一個單詞正好在 wordDict 中,dp[0] 設定成 true 是合理的
        dp[0] = true;

        for (int right = 1; right <= len; right++) {
            // 如果單詞集合中的單詞長度都不長,從後向前遍歷是更快的
            for (int left = right - 1; left >= 0; left--) {
                // substring 不擷取 s[right],dp[left] 的結果不包含 s[left]
                if (wordSet.contains(s.substring(left, right)) && dp[left]) {
                    dp[right] = true;
                    // 這個 break 很重要,一旦得到 dp[right] = True ,不必再計算下去
                    break;
                }
            }
        }

        // 第 2 步:回溯演算法搜尋所有符合條件的解
        List<String> res = new ArrayList<>();
        if (dp[len]) {
            Deque<String> path = new ArrayDeque<>();
            dfs(s, len, wordSet, dp, path, res);
            return res;
        }
        return res;
    }

    /**
     * s[0:len) 如果可以拆分成 wordSet 中的單詞,把遞迴求解的結果加入 res 中
     *
     * @param s
     * @param len     長度為 len 的 s 的字首子串
     * @param wordSet 單詞集合,已經加入雜湊表
     * @param dp      預處理得到的 dp 陣列
     * @param path    從葉子結點到根結點的路徑
     * @param res     儲存所有結果的變數
     */
    private void dfs(String s, int len, Set<String> wordSet, boolean[] dp, Deque<String> path, List<String> res) {
        if (len == 0) {
            res.add(String.join(" ",path));
            return;
        }

        // 可以拆分的左邊界從 len - 1 依次列舉到 0
        for (int i = len - 1; i >= 0; i--) {
            String suffix = s.substring(i, len);
            if (wordSet.contains(suffix) && dp[i]) {
                path.addFirst(suffix);
                dfs(s, i, wordSet, dp, path, res);
                path.removeFirst();
            }
        }
    }
}