问题描述:
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words. You may assume the dictionary does not contain duplicate words.
For example, given
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".
这道题大概的意思就是问一个字符串能不能被拆分成dict中的子字符串,可以的话返回true,不可以的话返回false。
其实一看到这道题,解题的方法就很明显了,用回溯法,不断地去尝试分割字符串s,并且在前一个子字符串满足的条件下,再继续分割剩余的子字符串,直到原字符串s被分割完,且分割得到的所有子字符串都在dict中,就满足条件了,返回true。下面是递归解决的代码:
public boolean wordBreak(String s, List<String> wordDict) {
//递归的方法,超时
if(s.length() == 0 || s == null)
return false;
HashSet<String> dict = new HashSet(wordDict);
for(int i = 1; i <= s.length(); i++) {
//递归地进行字符串分割
if(searchWord(s, 0, i, tempSet))
return true;
}
return false;
}
public boolean searchWord(String s, int beginIndex, int endIndex, HashSet<String> wordDict) {
String subString = s.substring(beginIndex, endIndex);
if(wordDict.contains(subString)) {
//当分割到最后一个子字符串时,且这个子字符串能在dict中找到,返回true
if(endIndex == s.length())
return true;
else {
for(int i = endIndex+1; i <= s.length(); i++) {
if(searchWord(s, endIndex, i, wordDict))
return true;
}
}
}
return false;
}
然而,很遗憾的是,这个方法超时了,因为这种递归回溯的方法,并没有对中间的结果进行保存,导致了大量的重复计算。
因此,我们需要将递归转换为非递归,要怎么转呢?递归是从后往前一步一步往回走,中间的状态不好保存,那我们就可以尝试从前往后走的非递归,它可以很好的保存中间的状态。我们需要一个result数组来保存状态,状态是什么呢,就是关于从0到对应索引组成的子字符串是否能够拆分成字典中单词的状态。那么当我们在判断后面的子字符串是否能够拆分时,如果需要判断前面的子字符串是否能够拆分,就不需要再去通过循环去判断,直接通过状态来判断,就可以少去很多的重复的计算。代码如下:
public boolean wordBreak(String s, List<String> wordDict) {
//非递归的方法
if(s==null || s.length()==0)
return true;
HashSet<String> dict = new HashSet(wordDict);
//保存动态规划每一步的状态,即从0到当前索引组成的子字符串能否被拆分成字典中的单词的状态
boolean[] result = new boolean[s.length()+1];
result[0] = true;
for(int i = 0; i < s.length(); i++) {
//从开头到当前索引截取子字符串
StringBuilder tempString = new StringBuilder(s.substring(0, i+1));
//判断当前子字符串能否被拆分成字典中的单词
for(int k = 0; k <= i; k++) {
if(result[k] && dict.contains(tempString.toString())) {
result[i+1] = true;
break;
}
//删除子字符串的第一个字符,继续判断
tempString.deleteCharAt(0);
}
}
return result[s.length()];
}
通过这种中间保存状态的方法,我们可以免去递归中很多重复的计算,大大地提高了算法的效率,因此超时的情况也不会再出现了。所以这道题的核心是:非递归的动态规划算法。
谢谢大家观看我的博客。如果有不明白的地方,或者是文中有错误的地方,欢迎指出,谢谢!如果大家喜欢我的博客,也可以给我点点赞。你们的点赞就是我的动力!