1. 程式人生 > >Leetcode 139:單詞拆分(最詳細的解法!!!)

Leetcode 139:單詞拆分(最詳細的解法!!!)

給定一個非空字串 s 和一個包含非空單詞列表的字典 wordDict,判定 s 是否可以被空格拆分為一個或多個在字典中出現的單詞。

說明:

  • 拆分時可以重複使用字典中的單詞。
  • 你可以假設字典中沒有重複的單詞。

示例 1:

輸入: s = "leetcode", wordDict = ["leet", "code"]
輸出: true
解釋: 返回 true 因為 "leetcode" 可以被拆分成 "leet code"。

示例 2:

輸入: s = "applepenapple", wordDict = ["apple", "pen"]
輸出: true
解釋: 返回 true 因為 "applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重複使用字典中的單詞。

示例 3:

輸入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
輸出: false

解題思路

首先想到的是暴力破解,我們通過索引遍歷字串,測試本次遍歷到的s[pre:cur]是不是wordDict中的字串,不是的話cur+1繼續判斷,否則我們pre=cur,直到cur=len(s)

class Solution:
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
len_s = len(s) pre, cur = 0, 1 while cur != len_s: if s[pre:cur] in wordDict: pre = cur else: cur += 1 if s[pre:cur] in wordDict: return True return False

當然這裡我們可以通過將wordDict儲存為dict結構來加速。但是這種思路是錯的,我們使用這種貪心策略,會忽略這樣的問題。

s = "aaaaaaa"
wordDict = ["aaaa","aaa"]

我們相當於每次取最短,結果就是["aaa", "aaa", "a"]

實際上這個問題和之前的 Leetcode 300:最長上升子序列(最詳細的解法!!!) 很類似。我們同樣可以通過動態規劃的方法解決這個問題。我們定義函式f(i),表示s[0:i]是否可以被拆分成字典中的單詞。所以我們需要遍歷字典中的單詞,例如

i = 7
s = "aaaaaaa"
wordDict = ["aaaa","aaa"]

我們遍歷到第一個"aaaa",此時我們要知道f(7)是否成立,我們只需要知道f(3)是否可以成立即可,如果f(3)成立,我們這個時候只需要將"aaaa"放入即可。而我們需要知道f(3)是否可以成立,我們就需要直到f(-1)能否成立,但是-1超出了邊界,所以我們判斷"aaa"能否放入,也就是我們判斷f(0)能否成立即可。而我們直到對於空字串來說,直到我們單詞字典中不選出單詞即可,所以f(0)=True。所以按照這個過程,我們可以很快寫出下面的程式碼

class Solution:
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        len_s = len(s)
        mem = [False]*(len_s+1)
        mem[0] = True
        for i in range(1, len_s + 1):
            for word in wordDict:
                if i >= len(word) and mem[i - len(word)] \
                	and word == s[i-len(word):i]:
                    mem[i] = True

        return mem[-1]

上面這個程式碼存在一些細節上的優化,當我們mem[i]=Ture後,我們可以直接退出迴圈了。另外我們可以將wordDict做成一個包含單詞長度的字典

class Solution:
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        len_s = len(s)
        mem = [False]*(len_s+1)
        mem[0] = True
        tmpDict = dict((i,len(i)) for i in wordDict)
        for i in range(1, len_s + 1):
            for word in wordDict:
                if i >= tmpDict[word] and mem[i - tmpDict[word]] \
                	and word == s[i-tmpDict[word]:i]:
                    mem[i] = True
                    break

        return mem[-1]

上述程式碼在c++實現的過程中最好不要使用vector<bool>,原因是vector<bool>並不是一個真正的容器,這是來自Effective STL的建議,所以我們可以使用bitset或者vector<int>deque<int>

這個問題我們也可以通過回溯法來求解。我們需要直到s能否分割,那麼我們只需要知道s[index:]能否分割,並且s[0:index]wordDict中的單詞。如果index==len(s),我們就返回true

class Solution:
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        return self._wordBreak(s, set(wordDict), 0)
        
    def _wordBreak(self, s, words, start):
        if start == len(s):
            return True

        for i in range(start + 1, len(s) + 1):
            sub = s[start:i]
            if sub in words and self._wordBreak(s, words, i):
                return True

        return False

同樣,我們可以通過記憶化搜尋的方法來優化這個問題。

class Solution:
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        return self._wordBreak(s, set(wordDict), 0, set())
        
    def _wordBreak(self, s, words, start, mem):
        if start == len(s):
            return True
        
        if start in mem:
            return False

        for i in range(start + 1, len(s) + 1):
            if i in mem:
                continue

            sub = s[start:i]
            if sub in words and self._wordBreak(s, words, i, mem):
                return True

        mem.add(start)
        return False

如有問題,希望大家指出!!!