---
# System prepended metadata

title: 【LeetCode】0139. Word Break
tags: [LeetCode, 面試刷題]

---

---
title: 【LeetCode】0139. Word Break
date: 2018-12-21
is_modified: false
disqus: cynthiahackmd
categories:
- "面試刷題"
tags:
- "LeetCode"
--- 

{%hackmd @CynthiaChuang/Github-Page-Theme %}

<br>

Given a  **non-empty**  string  _s_  and a dictionary  _wordDict_  containing a list of  **non-empty**words, determine if  _s_  can be segmented into a space-separated sequence of one or more dictionary words.
<!--more-->
<br> 

> **Note:**
>  - The same word in the dictionary may be reused multiple times in the segmentation.
> - You may assume the dictionary does not contain duplicate words.

<br>

**Example 1:**
```python
Input: s = "leetcode", wordDict = ["leet", "code"]
Output: True
Explanation:Return true because "leetcode" can be segmented as "leet code".
```

**Example 2:**
```python
Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: True
Explanation:Return true because "leetcode" can be segmented as "leet code".
```

**Example 3:**
```python
Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: False
```

<br>

**Related Topics:** `Dynamic Programming`



## 解題邏輯與實作
這題是要將題目給定的句子拆解成字典中的單字。第一個想到的就是我最愛的暴力法，拆、拆、拆就對了！


### 遞迴
也就是暴力解，想法很簡單，就是把句子拆成前後兩句下去查，一旦有一種拆法可以查的到就回傳 True，否則回傳 False。當然為了避免大量重複計算，另外用  HashTable 記錄查過得結果，解法如下：

1.  查詢此單字是否存在於字典中
	1. 有，則回傳 True
	2. 否，則向下執行
2.  使用一個指標由前向後遍歷此單字，並依指標位置，將單字拆成前後部份並呼叫遞迴傳入，唯有當前後部份皆可在字典中查到時才回傳 True，否則繼續向後遍歷。
3. 若遍歷完整個字串皆無法找到拆解法則回傳 False


```python=
class Solution:
      def __init__(self, mydict=None):
            self.mydict = mydict
            self.memo = {}

      def set_dict(self,mydict):
            self.mydict = mydict
            
      def wordBreak(self, s, wordDict):
            self.set_dict(set(wordDict))
            return self.check(s)

      def check(self, s):
            length = len(s)
            
            if s in self.memo:
                  return self.memo.get(s)

            if length <= 0 or s in self.mydict:
                  self.memo[s] = True
                  return True

            result = False
            for i in range(1, length):
                  result = self.check(s[:i]) and self.check(s[i:])
                  if result :
                        break

            self.memo[s] = result
            return result
```
但果不期然，效率有點差跑出了個 740 ms, 1.20%  的成績，摸摸鼻子改寫 DP 去。


### Dynamic Programming
這題大概是難得我這幾天整理的題目中，DP 解終於不是欠著的題目了 XDDD

這邊使用了一個 dp 陣列記錄結果，其中 dp[i] 中記錄著字串 s[:i] 是否能夠拆解，接下來若是 s[i:j] 也可以進行拆解，因為已知 s[:i] 可拆解，又s[i:j] 可拆解，故 s[:j] 可拆解，因此記錄 dp[j] 為 True，按這關係推導，最後就可以判斷目標字串是否可以被拆解。
 
```python=
class Solution:
      def wordBreak(self, s, wordDict):
            n = len(s)
            dp = [False] * (n + 1)
            dp[0] = True
      
            words = set(wordDict)
            for j in range(n):
                  for i in range(j, -1, -1):
                        if dp[i] and s[i:j + 1] in words:
                              dp[j + 1] = True
                              break
            return dp[n]
```
這效能果然好上不少，跑出了 68 ms, 15.49% 的成績。


### Dynamic Programming 改進版
看到我的 code 雖然有比上次進步，但其實無條件進位也才 16% 而已，有些好奇前面 code是怎樣的，所以手賤點了前段班的 code 出來看。

發現他雖然也是用 DP 解，但他加上了點巧思，與我不同的是他是由後向前遍歷，並寫加上了檢查距離的限制，限制在字典中最長的單字長度上，檢查再多也沒有意義，因為字典就沒有這麼長的單字咩。最後跑出來成績為 36 ms, 99.65% 。

 
```python=
class Solution:
      def wordBreak(self, s, wordDict):
            words = set(wordDict)
            maxLen = 0
            for word in wordDict:
                  maxLen = max(maxLen, len(word))
            n = len(s) 
            dp =   [False] * (n + 1)
            dp[n] = True 

            for i in range(n - 1, -1, -1):
                  for j in range(i + 1, min(i + maxLen, n) + 1): 
                        if dp[j] and s[i:j] in words:
                              dp[i] = True
                              break 
            return dp[0]
```



## 其他連結
1. [【LeetCode】0000. 解題目錄](/x62skqpKStKMxRepJ6iqQQ)

<br><br> 

> **本文作者**： 辛西亞．Cynthia  
> **本文連結**： [辛西亞的技能樹](https://cynthiachuang.github.io/LeetCode-0139-Word-Break) / [hackmd 版本](https://hackmd.io/@CynthiaChuang/LeetCode-0139-Word-Break)   
> **版權聲明**： 部落格中所有文章，均採用 [姓名標示-非商業性-相同方式分享 4.0 國際](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en) (CC BY-NC-SA 4.0) 許可協議。轉載請標明作者、連結與出處！
