题目描述
给定一个非空字符串s和一个包含非空单词列表的字典wordDict,判断s是否可以被分割成一个或多个在字典中出现的单词。
示例
示例1:
Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: 可以将s分割为"leet"和"code"这两个单词。
示例2:
Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: 可以将s分割为"applepen"和"apple"这两个单词。注意,我们只能使用字典中的单词一次。
示例3:
Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false
Explanation: 无法将s分割成字典中出现的单词。
解法
我们可以使用动态规划来解决这个问题。我们定义dp[i]表示s中前i个字符是否可以被拆分成一个或多个在字典中出现的单词。
那么对于每个位置i,我们需要枚举j从0到i-1,如果dp[j]=true并且从j到i的子串在字典中出现,那么就有dp[i]=true。
具体实现时,我们可以用一个set来存储字典中的所有单词,然后
- 遍历s中的每个位置i,并在其中枚举j。
- 如果dp[j]=true并且从j到i的子串在字典中出现,那么就有dp[i]=true。
- 最终我们只需要返回dp[n]即可,其中n为字符串s的长度。
代码
function wordBreak(s: string, wordDict: string[]): boolean {
const n = s.length;
const dictSet = new Set(wordDict);
const dp: boolean[] = new Array(n + 1).fill(false);
dp[0] = true;
for (let i = 1; i <= n; i++) {
for (let j = 0; j < i; j++) {// 判断子串是否可以分解
if (dp[j] && dictSet.has(s.substring(j, i))) {// 前一半可以,后一半存在
dp[i] = true;
break;
}
}
}
return dp[n];
}
时间复杂度
外层循环遍历整个字符串s,内层循环最多需要遍历s中的每个字符,因此总时间复杂度为。
空间复杂度
定义了一个长度为(n+1)的dp数组来记录前i个字符是否可以被拆分成一个或多个在字典中出现的单词,因此空间复杂度为。同时,使用了一个set来存储字典中的所有单词,空间复杂度为,其中m为字典中单词的个数。因此,总的空间复杂度为。