一、题目描述
给你一个字符串 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
中的所有字符串 互不相同
二、解题思路
1. 初始化动态规划数组:
- 创建一个布尔数组
dp
,长度为s.length() + 1
。 - 将
dp[0]
设置为true
,因为空字符串可以被看作是由空单词列表拼接而成。
2. 填充动态规划数组:
- 遍历字符串
s
的每个可能的结束位置i
,从1
到s.length()
。 - 对于每个
i
,再进行一次内部循环,遍历所有可能的前一个位置j
,从0
到i-1
。 - 在内部循环中,检查
dp[j]
是否为true
,且s.substring(j, i)
是否在字典wordDict
中。 - 如果上述条件成立,说明从
j
到i
的子串可以被字典中的单词拼接而成,因此将dp[i]
设置为true
。
3. 返回结果:
- 动态规划数组填充完毕后,
dp[s.length()]
的值即为所求,它表示整个字符串s
是否可以被字典中的单词拼接而成。 - 返回
dp[s.length()]
作为函数的输出。
这个动态规划解法的核心思想是将大问题分解为小问题,通过检查所有可能的前缀来逐步构建出整个字符串是否可以被拼接的答案。
三、具体代码
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;public class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> wordSet = new HashSet<>(wordDict);boolean[] dp = new boolean[s.length() + 1];dp[0] = true;for (int i = 1; i <= s.length(); i++) {for (int j = 0; j < i; j++) {if (dp[j] && wordSet.contains(s.substring(j, i))) {dp[i] = true;break;}}}return dp[s.length()];}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 将列表
wordDict
转换为哈希集合wordSet
的时间复杂度是 O(k),其中 k 是字典中单词的数量。这是因为需要对每个单词进行哈希运算。 - 初始化布尔数组
dp
的时间复杂度是 O(n),其中 n 是字符串s
的长度。 - 双层循环的时间复杂度是 O(n^2),因为外层循环执行了 n 次,内层循环在最坏情况下也可能执行 n 次。
substring
操作的时间复杂度是 O(k),其中 k 是子字符串的长度。在最坏情况下,k 可以达到 n,但通常情况下,k 会有一个上限,即字典中最长单词的长度。
综上所述,总的时间复杂度是 O(n^2 * m),其中 m 是字典中单词的平均长度。这是因为对于每个子字符串,我们需要检查它是否在字典中,这个操作的时间复杂度是 O(m)。
2. 空间复杂度
- 哈希集合
wordSet
的空间复杂度是 O(k),其中 k 是字典中单词的数量。 - 布尔数组
dp
的空间复杂度是 O(n),其中 n 是字符串s
的长度。
因此,总的空间复杂度是 O(n + k),即由动态规划数组和哈希集合构成的空间需求。
五、总结知识点
-
动态规划:这是一种算法设计技术,用于解决优化问题。它将问题分解为更小的子问题,并通过组合子问题的解来解决原始问题。在这个问题中,
dp
数组用于存储子问题的解,即字符串的前缀是否可以被字典中的单词拼接而成。 -
哈希集合:
HashSet
是 Java 中的一个集合实现,用于存储不重复的元素,并且可以快速判断一个元素是否存在于集合中。在这个问题中,wordSet
用于存储字典中的单词,以便快速检查一个子字符串是否是字典中的单词。 -
字符串操作:
substring
方法是 JavaString
类的一个方法,用于提取字符串中的一个子串。在这个问题中,它用于提取从位置j
到i
的子字符串,检查它是否在字典中。 -
数组的初始化:代码中使用
new boolean[s.length() + 1]
初始化了一个布尔数组dp
,所有元素默认为false
。然后,dp[0]
被显式设置为true
,因为空字符串可以被看作是由空单词列表拼接而成。 -
双层循环:外层循环遍历字符串
s
的每个可能的结束位置i
,内层循环遍历所有可能的前一个位置j
。这种结构用于填充动态规划数组dp
。 -
递推关系:动态规划的核心是找到子问题之间的递推关系。在这个问题中,
dp[i]
的值取决于dp[j]
(j < i
)的值和子字符串s.substring(j, i)
是否在字典中。 -
边界条件:在动态规划中,通常需要设置边界条件。在这个问题中,
dp[0]
被设置为true
,这是递推的起始条件。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。