题目描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
总结
逆向思维不只数组倒着遍历,结果的倒着求…还可以像SDC1那样end,start反过来。
Sample & Demo Code 1
这个方法的想法是对于给定的字符串(s)可以被拆分成子问题 s1 和 s2 。如果这些子问题都可以独立地被拆分成符合要求的子问题,那么整个问题 sss 也可以满足。也就是,如果 “catsanddog” 可以拆分成两个子字符串 “catsand” 和 “dog” 。子问题 “catsand” 可以进一步拆分成 “cats” 和 “and” ,这两个独立的部分都是字典的一部分,所以 “catsand” 满足题意条件,再往前, “catsand” 和 “dog” 也分别满足条件,所以整个字符串 “catsanddog” 也满足条件。
public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true; // 抽象出s的第一个子字符串的前一个
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
优化
因为wordDict中的字符串长度是有限的。
end只需要从minLen开始搜索,
start只需要从end-maxLen开始搜索,到end-minLen就可以了。
public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int maxLen = -1, minLen = Integer.MAX_VALUE;
for(String ws : wordDict) {
if(ws.length() > maxLen) maxLen = ws.length();
if(ws.length() < minLen) minLen = ws.length();
}
Set<String> wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true; // 抽象出s的第一个子字符串的前一个
for (int end = minLen; end <= s.length(); end++) {
for (int start = Math.max(end-maxLen, 0); start <= end-minLen; start++) {
if (dp[start] && wordDictSet.contains(s.substring(start, end))) {
dp[end] = true;
break;
}
}
}
return dp[s.length()];
}
}
Sample & Demo Code 2
记忆化回溯
class Solution {
Boolean[] memo;
public boolean wordBreak(String s, List<String> wordDict) {
this.memo = new Boolean[s.length()];
return helper(0, s, wordDict);
}
private boolean helper(int start, String s, List<String> wordDict) {
if(start >= s.length()) return true;
if(memo[start] != null) return memo[start];
for(int end = start + 1; end <= s.length(); end++) {
if(wordDict.contains(s.substring(start, end)) && helper(end, s, wordDict))
return memo[start] = true;
}
return memo[start] = false;
}
}
Sample & Demo Code 3
宽度优先搜索。将字符串可视化成一棵树,每一个节点是用 end 为结尾的前缀字符串。当两个节点之间的所有节点都对应了字典中一个有效字符串时,两个节点可以被连接。
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDicts = new HashSet(wordDict);
boolean[] visited = new boolean[s.length()];
Queue<Integer> queue = new LinkedList<>();
queue.add(0);
while(!queue.isEmpty()) {
int start = queue.remove();
if(visited[start] == false) {
for(int end = start+1; end <= s.length(); end++) {
if(wordDicts.contains(s.substring(start, end))) {
if(end == s.length()) return true;
queue.add(end);
}
}
visited[start] = true;
}
}
return false;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-break
链接:https://leetcode-cn.com/problems/word-break/solution/dan-ci-chai-fen-by-leetcode/