leetcode 单词拆分

题目描述

给定一个非空字符串 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”。
注意你可以重复使用字典中的单词。

动态规划

  1. 状态:可以设置dp[i]表示前i位是否可以用wordDict单词表示

  2. 状态转移方程:设j>i,若dp[i]为True,那么当i到j之间的字符也在字典中,则dp[j]也为True。

    那么就可以得出方程

    dp[j]=dp[i] && s[i:j] in wordDict (j>i 且j<=n)

  3. 由此得出以下代码实现

    时间复杂度为 O ( n 2 ) O(n^2) O(n2)

    空间复杂度为 O ( n ) O(n) O(n)

def wordBreak(self, s: str, wordDict: 	List[str]) -> bool:
    # 1. 初始化dp长度为字符串长度+1的False数组(其中dp[0]=True)
    n = len(wordDict)
    dp = (n + 1) * [False]
    dp[0] = True
    # 2. 从0到字符串长度开始遍历计算是否可以用wordDict表示,遍历索引为i
    for i in range(n):
        # 2.1 从i+1开始遍历后面的字符串,区间为[i+1,n+1),索引为j
        for j in range(i + 1, n + 1):
            # 2.2 若前i位字符可以用wordDict单词表示并且从i到j中的字符也在wordDict单词之中,则设置dp[j]为True
            if s[i:j] in wordDict and dp[i] == True:
                dp[j] = True
    return dp[n + 1]

DFS

dfs是比较容易想到的方法

比如对于字符串"helloworld"能否break,就可以拆分为

  1. "h"是否是字典中单词,剩下的子串是否能break
  2. "he"是否是字典中单词,剩下的子串是否能break
  3. 以此类推

可以得到如下代码

def wordBreakMemo(s: str, wordDict: List[str]) -> bool:
    n=len(s)
    def wb(start: int)->bool:
        if start == n:
            return True
        for i in range(start+1,n+1):
            if s[start:i] in wordDict and wb(i):
                return True
        return False
    return wb(0)

但是这个当碰到

"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab,[“a”,“aa”,“aaa”,“aaaa”,“aaaaa”,“aaaaaa”,“aaaaaaa”,“aaaaaaaa”,“aaaaaaaaa”,“aaaaaaaaaa”]

便会超时。

分析原因是因为做了大量的重复计算,我们可以引入数组存储重复结果来优化。比如对第一次第二次都是从字典中取"a"和第一次从字典中取"aa",他们剩余子串结果是一样的。

于是得出以下代码

def wordBreakMemo(s: str, wordDict: List[str]) -> bool:
    n=len(s)
    memo=(n+1)*[None]
    memo[n]=True
    def wb(start: int)->bool:
        if memo[start] is not None:
            return memo[start]

        for i in range(start+1,n+1):
            if s[start:i] in wordDict and wb(i):
                memo[start]=True
                return True
        memo[start]=False
        return False
    return wb(0)

BFS

DFS可以用,那么BFS自然也可以用。我们可以从一个字符开始扫描字符串,若是字符在字典中,那就放入队列。接着出列,重复上述操作。

def wordBreak(s: str, wordDict: List[str]) -> bool:
    n = len(s)
    queue = [0]
    while len(queue) != 0:
        i = queue.pop()
        for j in range(i + 1, n + 1):
            if s[i:j] in wordDict:
                if j < n:
                    queue.append(j)
                else:
                    return True
    return False

在BFS也存在与DFS一致的问题,我们可以通过一个数组存储着BFS访问过的节点,若存在重复节点,则直接跳过。

def wordBreakMemo(s: str, wordDict: List[str]) -> bool:
    n = len(s)
    queue = [0]
    visited=[]
    while len(queue) != 0:
        i = queue.pop()
        if i not in visited:
            visited.append(i)
            for j in range(i + 1, n + 1):
                if s[i:j] in wordDict:
                    if j < n:
                        queue.append(j)
                    else:
                        return True
    return False

Ref

  1. https://leetcode-cn.com/problems/word-break
  2. https://leetcode-cn.com/problems/word-break/solution/dong-tai-gui-hua-ji-yi-hua-hui-su-zhu-xing-jie-shi/
  3. https://leetcode-cn.com/problems/word-break/solution/shou-hui-tu-jie-san-chong-fang-fa-dfs-bfs-dong-tai/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值