您的位置:首页 > 职场人生

LeetCode 139 Word Break

2014-02-25 11:31 585 查看
题目

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.1暴力解法。把每种情况都考虑。比如”ab”,可能“a b”或者”ab”。

1.2 时间复杂度来说,可以考虑每个字符可能与前一个相连或者不相连,所以n个字符,可以有2^n的可能性,也就是O(2^n)。证明时间复杂度可以用数学归纳法证明。

代码1

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;
}


思路2

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)

代码2

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

;
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];
}
}


思路3

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的技巧让 空间换时间。

代码3

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

;
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
;
}
}


思路4

4.1我称之为“字典贪婪”解法。也是需要一个一维数组,来表示前n个字符串是否可以切分。从第一个字符开始,每次遍历所有字典,把所有可能到达的字符串的位子都在数组中标示出来。然后再检查下一个字符,如果false,表示不能到达它,就不用看了;如果true,继续如此。

4.2 考虑时间复杂度。假设最差情况每个字符都要检查,然后字典含量为X,那么时间复杂度就是(Xn)。

4.3 当字典的内容远远少于字符串的长度的时候,可以考虑使用这种算法

代码4

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
;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode 面试笔试