LeetCode 139 Word Break
方法1:dfs + memo
时间复杂度:O(n2),我不知道单从记忆化上怎么分析这个复杂度,但是应该跟DP的时间复杂度一样
空间复杂度:O(n)
想法:这个题我觉得最直观的解法就是DFS,就是说搜到一个下标的时候,看着整个wordDict里面有没有一个word,使得当前下标往后的几个元素能正好对上word,如果能对上,就先认为这里能匹配,然后继续往后搜。有两个优化:
- 因为是只要返回true or false,只要在dfs当中找到一种可以的做法就结束了,不需要再进行任何搜索,因此我们采用boolean型DFS实现提前结束。
- 使用记忆化数组,我这里使用一个数组,长度与原输入的String s长度一样。然后memo有三种可能状态:不知道(还没搜过这个地方)、不行、行。我用的是int型数组,memo[i]=1代表从i这个下标(包含i)一直到s的最后,可以用wordDict里面的词拼出来,=-1代表不行,=0代表我不知道,还没搜过。
代码:
class Solution {
private int[] memo;
public boolean wordBreak(String s, List<String> wordDict) {
memo = new int[s.length()];
Set<String> wordSet = new HashSet<>();
for (String word : wordDict) {
wordSet.add(word);
}
return dfs(s, 0, wordSet);
}
private boolean dfs(String s, int index, Set<String> wordSet) {
int n = s.length();
if (index == n) {
return true;
}
if (memo[index] == -1) {
return false;
}
if (memo[index] == 1) {
return true;
}
for (String word : wordSet) {
int len = word.length();
if (index + len <= n && word.equals(s.substring(index, index + len))) {
if (dfs(s, index + len, wordSet)) {
memo[index] = 1;
return true;
}
}
}
memo[index] = -1;
return false;
}
}
方法2:DP
时间复杂度:O(n2)
空间复杂度:O(n2)
想法:这个做法我觉得直接想不是很直观,但如果能想到上面一种做法的话,这个应该也能想到。我感觉DP和DFS+memo有时候就是写法的不一样,一个是按递推式来写,一个是写成递归,但是里面的想法是一样的。dp长为n+1,dp[i]代表的是s字符串的substring(0,i)能不能由wordDict实现wordBreak。递推式的想法是,假设说当dp[j]=true的时候,在它之前一定有某个i,使得dp[i]是true,然后s.substring(i,j)在字典里。对于j的所有i,但凡有一个i能达到这一点,dp[j]就应该是true。这时候我们会发现,假如说字典里出现了字符串的前j个元素代表的子字符串,原则上来说肯定是可以的,我们应当认为这种情况下dp[j]为true,因此对照上面的递推式,在此问题中,我们应该认为dp[0]=true。
代码:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordSet = new HashSet<>();
for (String word : wordDict) {
wordSet.add(word);
}
int n = s.length();
boolean[] dp = new boolean[n + 1];
dp[0] = true;
for (int j = 1; j <= n; j++) {
for (int i = 0; i < j; i++) {
if (dp[i] && wordSet.contains(s.substring(i, j))) {
dp[j] = true;
break;
}
}
}
return dp[n];
}
}
LeetCode 140 Word Break II
方法:dfs + memo
时间复杂度:O(2n)
空间复杂度:O(2n)
想法:跟上一题不一样的地方在于求出所有解,那么首先我们应该知道递归的时候返回的解里面,从index往后的都有哪些解,这样在递归的上一层就可以往这些解里面往字符串的前面加单词。因此首先肯定不能采用boolean型的搜索,改为List型,不管怎么样都会做全部的搜索,不再搜到一个就结束全局。然后memo也得记录index->List。总的来说跟上一问的框架还是比较像,但不会再出现提前结束搜索,因此时间复杂度也涨到了2^n。
代码:
class Solution {
private Map<Integer, List<String>> memo = new HashMap<>();
public List<String> wordBreak(String s, List<String> wordDict) {
Set<String> wordSet = new HashSet<>();
for (String word : wordDict) {
wordSet.add(word);
}
return dfs(s, 0, wordSet);
}
private List<String> dfs(String s, int index, Set<String> wordSet) {
if (memo.containsKey(index)) {
return memo.get(index);
}
List<String> res = new ArrayList<>();
int n = s.length();
String suffix = s.substring(index, n);
if (wordSet.contains(suffix)) {
res.add(suffix);
}
for (String word : wordSet) {
int len = word.length();
if (index + len <= n && word.equals(s.substring(index, index + len))) {
List<String> next = dfs(s, index + len, wordSet);
for (String ne : next) {
res.add(word + " " + ne);
}
}
}
memo.put(index, res);
return res;
}
}