目录
题目
给一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 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
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s
和wordDict[i]
仅由小写英文字母组成wordDict
中的所有字符串 互不相同
思路
在这个问题中,选择使用动态规划(Dynamic Programming, DP)的方法来解决。动态规划是解决此类“分割”或“子问题重叠”问题的强大工具,特别是在需要判断一个字符串是否可以通过某种方式(这里是空格拆分成字典中的单词)完全构建起来时。
解题过程
-
初始化
- 定义一个布尔数组
dp
,其中dp[i]
表示字符串s
的前i
个字符(即s[0:i]
)是否可以被空格拆分成若干个字典中出现的单词。 - 初始化
dp[0] = True
,因为空字符串可以被视为已经拆分完成(尽管实际上没有拆分)。
-
预处理
将字典 wordDict
转换为一个哈希集合(HashSet),以便在 O(1) 时间复杂度内检查一个单词是否存在于字典中。
-
动态规划状态转移
- 遍历字符串
s
的每个位置i
(从 1 到len(s)
)。 - 对于每个位置
i
,遍历从 0 到i-1
的每个位置j
作为分割点。 - 检查
s[j:i]
(即从位置j
到i-1
的子串)是否在哈希集合中。 - 如果
s[j:i]
在哈希集合中且dp[j]
为True
,则设置dp[i] = True
。 - 如果在任何时候找到
dp[i] = True
,则无需继续检查更小的j
值(这是一个可选的优化,但通常不会显著改变总体时间复杂度)。
-
最终结果
dp[len(s)]
的值将告诉我们整个字符串 s
是否可以被空格拆分成字典中的单词。
复杂度
-
时间复杂度: O(n^2)
其中 n
是字符串 s
的长度。我们需要遍历字符串 s
的每个位置 i
(O(n)),并且在每个位置 i
上,我们需要遍历从 0 到 i-1
的每个位置 j
(O(n)),总共 O(n^2) 次操作。哈希表查找是 O(1) 的,因此不增加总体时间复杂度的阶数。
-
空间复杂度: O(n)
其中 n
是字符串 s
的长度。我们需要一个长度为 n+1
的布尔数组 dp
来存储状态,以及一个哈希集合来存储字典中的单词。哈希集合的空间复杂度通常取决于字典中单词的数量和长度,但在这里我们可以将其视为与 n
成正比(在最坏情况下,字典包含所有可能的长度为 1 到 n
的子串),但通常情况下会远小于这个界限。因此,总体空间复杂度可以认为是 O(n)。
code
class Solution(object):
def wordBreak(self, s, wordDict):
wordSet = set(wordDict) # 将字典转换为集合以加速查找
dp = [False] * (len(s) + 1)
dp[0] = True # 空字符串可以被拼接
for i in range(1, len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in wordSet:
dp[i] = True
break
return dp[len(s)]