Leetcode 139.单词拆分


题目描述:

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明

  • 拆分时可以重复使用字典中的单词。
  • 你可以假设字典中没有重复的单词。

示例 1:

1
2
3
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例 2:

1
2
3
4
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
  注意你可以重复使用字典中的单词。

示例 3:

1
2
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

链接:

https://leetcode-cn.com/problems/word-break


题目分析

  字典中的单词不会重复,并且可以重复使用,我们可以考虑使用一个哈希表存储这个字典。而对于字符串 s,我们用动态规划的思想,dp[i] 表示 s 的前 i 个字母 s[0 ~ i-1] 是否可以使用字典中的单词表示,则我们知道,如果 dp[j] 为真,并且 s[j ~ i-1] 刚好是字典中的一个单词,则 dp[i] 也为真。而 dp[0] 作为起始条件,表示空串的结果为真。对于每一个 dp[i],我们都要遍历 dp[0] ~ dp[i-1],寻找以这个位置分割是否符合条件,一旦符合条件则可以置为真并提前退出遍历。遍历所有的 dp[i] 之后,我们的答案就是 dp[s.size()]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> words;
for(auto word : wordDict){
words.insert(word);
}
vector<bool> dp(s.size()+1, false);
dp[0] = true;
for(int i = 1; i <= s.size(); i++){
for(int j = 0; j < i; j++){
if(dp[j] && words.find(s.substr(j, i-j)) != words.end()){
dp[i] = true;
break;
}
}
}
return dp[s.size()];
}
};

  时间复杂度:$O(n^2)$,其中 $n$ 为字符串的长度。动态规划的状态数为 $n+1$,而对于每个状态最多都要枚举 $O(n)$ 个分割点,其实也即双层遍历,时间复杂度为 $O(n^2)$。
  空间复杂度:$O(n+k)$,其中 $n、k$ 分别为字符串的长度与字典的大小。动态规划数组大小为 $n+1$,而哈希表大小为 $k$。

  PS:这道题还存在优化空间,比如如果字典中的单词量较少,内层循环也可以设计成匹配字典中每个单词的形式;比如可以进行剪枝,内层循环中分割的位置无需从 0 开始,可以从字典中最长的单词长度作为右半部分开始。