Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet
code".
1.1暴力解法。把每种情况都考虑。比如”ab”,可能“a b”或者”ab”。
1.2 时间复杂度来说,可以考虑每个字符可能与前一个相连或者不相连,所以n个字符,可以有2^n的可能性,也就是O(2^n)。证明时间复杂度可以用数学归纳法证明。
public boolean wordBreak(String s, Set<String> dict) {
return worddfs(s,dict);
}
public boolean worddfs(String s,Set<String> dict){
if(s.equals("")){
return true;
}
for(int i=0;i<s.length();i++){
String temp = s.substring(0,i+1);
if(dict.contains(temp)){
if(i==s.length()-1){
return true;
}
if(worddfs(s.substring(i+1),dict)){
return true;
}
}
}
return false;
}
1 时间不行,有很多重复检查了。那么考虑从底向上的字典dfs。需要一个二维数组mp。
2 mp[i][j] 代表 S 从i 到j 是否可以匹配。只要存在k mp[i][k] 和 mp[k][j] 都为true的时候,mp[i][j] 也为true。
3 我们需要求的就是mp[0][n-1]
4 时间是O(n3)
public class Solution {
public boolean wordBreak(String s, Set<String> dict) {
if(s.length()==0){
return false;
}
int n = s.length();
boolean[][] ans = new boolean[n][n];
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
if(dict.contains(s.substring(i,j+1))){
ans[i][j]=true;
}
}
}
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
for(int k=i;k<=j;k++){
if(!ans[i][j]){
ans[i][j]=ans[i][k]&&ans[k+1][j];
}
}
}
}
return ans[0][n-1];
}
}
1 虽然accept,但是时间不够好。继续思考。
2 发现其实我们要求的只是ans[0][n-1]。然后对于每个0行的元素,也就是string 0--i,只有在他之前的0-j是true和ans[j][i]是true的时候才true。
3 所以我们只要再多一个memo[n+1]的数组,来记录每个0-j是否为true,结合上面的ans数组,就可以判断了。
4 时间缩小到O(n2), dp的技巧让 空间换时间。
public class Solution {
public boolean wordBreak(String s, Set<String> dict) {
if(s.length()==0){
return false;
}
int n = s.length();
boolean[][] ans = new boolean[n][n];
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
if(dict.contains(s.substring(i,j+1))){
ans[i][j]=true;
}
}
}
boolean[] memo = new boolean[n+1];
memo[0]=true;
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
if(memo[j]&&ans[j][i]){
memo[i+1]=true;
}
}
}
return memo[n];
}
}
4.1我称之为“字典贪婪”解法。也是需要一个一维数组,来表示前n个字符串是否可以切分。从第一个字符开始,每次遍历所有字典,把所有可能到达的字符串的位子都在数组中标示出来。然后再检查下一个字符,如果false,表示不能到达它,就不用看了;如果true,继续如此。
4.2 考虑时间复杂度。假设最差情况每个字符都要检查,然后字典含量为X,那么时间复杂度就是(Xn)。
4.3 当字典的内容远远少于字符串的长度的时候,可以考虑使用这种算法
public class Solution {
public boolean wordBreak(String s, Set<String> dict) {
boolean[] canBreak = new boolean[s.length()+1];
canBreak[0] = true;
for(int i=0; i<s.length(); i++){
if(canBreak[i] == false){
continue;
}
for(String dictS : dict){
int len = dictS.length();
int end = i + len;
if(end > s.length()){
continue;
}
if(s.substring(i, end).equals(dictS)){
canBreak[end] = true;
}
}
}
return canBreak[s.length()];
}
}
1其中思路2和思路3 ,我称之为“贪婪字符串”解法。”abcd“,从最后一个字符考虑。如果可以切分,那么
2.1.1要么”d”字典有,并且”abc”的某种切分成立;
2.1.2要么”cd”字典有,并且”ab”的某种切分成立;
2.1.3 要么“bcd”字典有,并且“a”的某种切分成立;
2.1.4 要么“abcd”字典有,并且“”的某种切分成立;
可以看到之前的切分也可以用这个方法来完成,并且有循环的含义在里面。所以可以使用递归或者循环来实现。考虑用一个数组来表示到某个单词为止可以实现切分,即能完成此类算法。
3 考虑时间复杂度。可以看到在前1个字符的时候,检查1种可能;前2个字符的时候,检查2种可能。。。前n个字符的时候,检查n种可能。于是1+2+。。。+n: O(n^2)的时间复杂度。
4 这个时间复杂度是可以接受的。
5 另外一种写法:
// 2 贪婪字符串解法,使用一个数组来实现
public boolean wordBreak1_2(String str, HashSet<String> record) {
if(str.length()==0){
return false;
}
int n = str.length();
// 检验数组,第n位就代表前n个字符代表的字符串是否可以某种方式切分
boolean[] sepHere = new boolean[n+1];
sepHere[0]=true;
// i 代表前几个字符。比如i=3的时候,就代表前3个字符
for(int i=1;i<=n;i++){
// 遍历所有可能的切分方式
for(int j=i-1;j>=0;j--){
if(sepHere[j]==true && record.contains(str.substring(j,i))){
sepHere[i]=true;
break;
}
}
}
return sepHere[n];
本文探讨了字符串能否被字典中的单词完全切割的问题,提供了四种不同复杂度的算法解决方案,包括暴力解法、改进版动态规划、优化的空间换时间策略及字典贪婪算法。

287

被折叠的 条评论
为什么被折叠?



