139. [动态规划][记忆化搜素]单词拆分
139. 单词拆分
方法一:动态规划
我们定义\(dp[i]\)表示字符串\(s\)前\(i\)个字符组成的字符串\(s[0…i-1]\)能否被拆分若干个\(wordDict\)中的单词。
接下来要考虑状态转移方程,每次转移时需要枚举包含位置 \(i-1\)的最后一个单词,看它是否出现在\(wordDict\)中以及除去这部分的字符串是否合法即可。
公式化来说,我们需要枚举\(s\)的分割点\(j\),看\(s\)分割出的\(s_i\)与\(s_j\)是否合法,那么他们重新拼接起来自然也合法。由于在循环中,我们已经计算出了\(dp[0…1]\)的值,因此字符串\(s_1\)是否合法可以直接由\(dp[j]\)得知,剩下我们只需要判断\(s_2\)是否合法就行。
因此得出状态转移方程的公式为:\(dp[i]=dp[j]\ \&\& \ check(s[j..i−1])\)。
其中\({check}(s[j…i-1])\) 表示子串 \(s[j…i-1]\)是否出现在\(wordDict\)中。
对于检查一个字符串是否出现在给定的字符串列表里一般可以考虑哈希表来快速判断,具体实现如下:
// 执行耗时:10 ms,击败了53.29% 的Java用户
// 内存消耗:39 MB,击败了31.10% 的Java用户
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1]; // dp[i]表示从0-i位的子串能否拆分
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++){
if (dp[j] && wordDict.contains(s.substring(j, i))){
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
优化:
考虑到每次遍历\(s\)字符串,检查是否在\(Set\)中存在,每次都是从头遍历到尾,这样必定回带来大量的无效遍历,如果当前遍历的长度大于字段中字符串的最大长度,则一定不可能匹配成功,所以,\(dp[i]\)只需要往前探索到词典里最长的单词即可。
// 执行耗时:1 ms,击败了99.68% 的Java用户
// 内存消耗:36.4 MB,击败了99.80% 的Java用户
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int len = s.length(), maxw = 0;
boolean[] dp = new boolean[len + 1];
dp[0] = true;
Set<String> wordDictSet = new HashSet();
for(String str : wordDict){
wordDictSet.add(str);
maxw = Math.max(maxw, str.length());
}
for(int i = 1; i < len + 1; i++){
for(int j = i; j >= 0 && j >= i - maxw; j--){
if(dp[j] && wordDictSet.contains(s.substring(j, i))){
dp[i] = true;
break;
}
}
}
return dp[len];
}
}

浙公网安备 33010602011771号