LeetCode 139 Word Break

本文探讨了字符串能否被字典中的单词完全切割的问题,提供了四种不同复杂度的算法解决方案,包括暴力解法、改进版动态规划、优化的空间换时间策略及字典贪婪算法。
题目


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

思路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[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


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[n];









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值