---
# System prepended metadata

title: 刷題筆記 - LeetCode 139. Word Break
tags: [leetcode, dynamic programming]

---

# 刷題筆記 - LeetCode 139. Word Break

https://leetcode.com/problems/word-break


## Thoughts
給一個 string `s`, 看他這個 s 能不能被 `wordDict` 給的字組合而成

Example 1:

>Input: s = "leetcode", wordDict = ["leet","code"]
Output: true
Explanation: Return true because "leetcode" can be segmented as "leet code".

Example 2:

>Input: s = "applepenapple", wordDict = ["apple","pen"]
Output: true
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
Note that you are allowed to reuse a dictionary word.

Example 3:

>Input: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"]
Output: false

\
從 Example 3 可看出，`s` 要可以被 wordDict 中的字拆分
如果更改一下 Test case
>Input: s = "catsanddog", wordDict = ["cats","dog","sand","and","cat"]
Output: true
`s` 可以分成 `cats`, `and`, `dog`，這個組合全都有出現在 `wordDict` 中，因此回傳是 `true`


## Solution 1 - DP, bottom-up

### Logic
拆分問題 - 
> `s[0:i]` 是不是 wordDict 中出現的字？
> 是的話，`s[i:]` 能不能繼續拆？

假設使用 DP，先建立 DP array
```
dp = [False] * (len(s) + 1)
```
利用每個 `dp[i]` 來記錄 `s[0:i]` 是不是存在 wordDict 中
\
為什麼 dp array 長度是設定成 `len(s) + 1`?
因為如果 `len(s) == 0` 那一定可以被拆解 (`dp[0]=0`)
\
假設 `s` 長度為 8，整個遍歷的起始會是
```
0 1 2 3 4 5 6 7 8
  ↑                 
  i      
```
從 1 開始，檢查`s[0:1]` 是否在字典中


遍歷 `s`，假設走到的位置 `i`，可以被正確拆分的話
=> 代表 `i` 之前的 substring，也是可以被正確拆分
那再去看 substring，就是一個重複的問題


```
0 1 2 3 4 5 6 7 8
      ↑       ↑ 
      j       i
```
如果 `i` 走到 7
那就必須檢查 `s[0:7]` 是否能被正確拆分
**代表必須再一個迴圈來檢查 substring** (上圖 `j`)
而我們使用 `dp[i]` 來儲存 `s[0:i]` 是否存在字典中
那 substring 就可以被拆成：`s[0:j]` & `s[j:i]`
所以假設 `s[0:j]` 在字典中 (也就是 `dp[j]==True`) ，那需要確認的就是 `s[j:i]` 是不是在字典中，是的話那 `dp[i]` 就也應該設成 True

<br>

### Code
```python!=
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        wordSet = set(wordDict)
        dp = [False] * (len(s) + 1)
        dp[0] = True

        for i in range(1,len(s) + 1):
            for j in range(0, i):
                if dp[j] and s[j:i] in wordSet:
                    dp[i] = True
                    break

        return dp[len(s)]

```

line 7: 從 1 開始遍歷，到 len(s) (dp array 長度是 len(s)+1)
line 8: 第二層迴圈去看 `s[:i]` 的 substring，確認是不是能被拆分。因為先前已經使用 `dp[i]` 來儲存該位置是否確定可被字典中的字所組合，所以可以用 `dp[j]` 來判斷，而剩下的字串就是 `s[j:i]`
line 11: 如果確定至少有一個組合是可被字典拆分，就不用再繼續第二層迴圈，直接 break

最後回傳 `dp[len(s)]` 就是答案

### Time & Space Complexity
- Time: O(n^2)
    - n: length of the string`s`
- Space: O(n + k)
    - n: 建了一個 `len(s)+1` 的 DP array
    - k: 建立 `wordSet`，k 表示 wordDict 字的數量

### Optimization
第二層迴圈 `for j in range(0:i):` 可以不用跑完所有的 `0 ~ i`
假設字典裡面最長的單字長度為 `m`，那就不用檢查比 `m` 還長的 `s[j:i]`，因為一定不存在於字典中

## Solution 2 - Recursion

(working)



