classSolution: defwordBreak(self, s: str, wordDict: List[str]) -> List[str]: n = len(s) wordSet = set(wordDict) @cache # Memoization defdfs(i: int) -> List[List[str]]: # 返回 s[i:] 的所有可能劃分 if i == n: return [[]] res = [] for j inrange(i, n): # 枚舉當前單字的結尾位置 if s[i:j+1] in wordSet: for words in dfs(j + 1): res.append([s[i:j + 1]] + words) return res return [' '.join(words) for words in dfs(0)] # 將劃分的結果轉為字串
classNode { // Trie Node public: vector<Node*> children; bool isEnd; Node() : children(26), isEnd(false) {} }; classSolution { public: int n; string s; Node* root = newNode(); // Trie vector<string> ans; vector<string> wordBreak(string s, vector<string>& wordDict){ n = s.size(); this->s = s; for (string& word : wordDict) { Node* cur = root; for (char ch : word) { int idx = ch - 'a'; if (cur->children[idx] == nullptr) { cur->children[idx] = newNode(); } cur = cur->children[idx]; } cur->isEnd = true; // 標記當前 Trie 節點是某個單字的結尾 } dfs(0, root, "", ""); return ans; } voiddfs(int i, Node* cur, string word, string path){ // Backtracking if (i == n) { if (cur == root) ans.push_back(path); return; } if (!cur->children[s[i]-'a']) return; // 如果當前字元不在 Trie 中,則直接返回 (剪枝) word += s[i]; // 當前字元加入 word Node* nxt = cur->children[s[i]-'a']; dfs(i + 1, nxt, word, path); // 不劃分,繼續往下找 if (nxt->isEnd) { // 當前字元是單字的結尾,可以劃分 path += (path.empty() ? "" : " ") + word; dfs(i + 1, root, "", path); // 劃分,繼續從 Trie 的根節點開始 } } };
方法三:狀態壓縮,枚舉劃分點
上面兩種方式都是在需要劃分時進行遞迴,那不妨嘗試直接枚舉所有可能的劃分點,這樣就不需要進行遞迴,可以直接得到答案。而長度為 n 的字串 s,可以劃分的劃分點有 n−1 個,因此可以使用狀態壓縮的方式枚舉所有可能的劃分點,即枚舉所有 2n−1 個子集。
對於每個子集 i,若 i 的第 j 個位為 1,則表示在 s[j] 後需要進行劃分:
Python 的寫法中是將所有 s[j] 都先添加到 word 中,在需要劃分時檢查 word 是否在 wordDict 中,若在則可以添加到 path 中,並重置 word 為空字串;否則則代表這個劃分是無效的,直接跳過。
C++ 的寫法中是只使用一個字串 res ,並且持續將 s[j] 添加到 res 中,使用 k 來保存上個劃分在 res 中的位置。在檢查是否為有效劃分時,是檢查 s[k+1:] 是否在 wordDict 中,若為有效劃分且 j=n−1 則可以在 res 後添加空格。
同樣地,為了更快檢查 word 是否在 wordDict 中,可以先將陣列 wordDict 轉換為集合 wordSet。
複雜度分析
時間複雜度 O(n⋅2n),枚舉所有 2n−1 個子集,每個子集需要 O(n) 的時間。
空間複雜度 O(n),每次枚舉子集時需要 O(n) 的空間來保存劃分的結果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution: defwordBreak(self, s: str, wordDict: List[str]) -> List[str]: n = len(s) wordSet = set(wordDict) ans = [] for i inrange(0, 1 << (n - 1)): # 枚舉所有劃分點形成的子集合 path = [] # 劃分結果 word = ""# 當前單字 for j inrange(n): word += s[j] if i & (1 << j) or j == n - 1: # 在 s[j] 之後要劃分 if word notin wordSet: # 無法劃分 break path.append(word) word = "" else: ans.append(" ".join(path)) return ans