Leetcode 139:单词拆分(最详细的解法!!!)

Problems 同时被 2 个专栏收录
699 篇文章 6 订阅
654 篇文章 194 订阅

给定一个非空字符串 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

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

  • 1
    点赞
  • 2
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值