这是leetcode上的一道题
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
注意你可以重复使用字典中的单词。示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-break
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
刚开始的时候,我的思路是这样的:如果原字符串s能被拆分成wordDict里的单词,那么wordDict里的单词肯定就是原字符串s的子串。用KMP算法,将wordDict里面的单词和原字符串匹配,如果所有单词都不能匹配,则表示原字符串不能被拆分成wordDict里的单词,返回false;如果可以找到一个单词,是字符串s的子串,那么将s去掉这个子串,继续验证去掉这个子串的s能不能被wordDict里的单词匹配。当最后s被拆分为一个空字符串后,说明这个单词是可以被拆分的。
代码如下:
1 class Solution { 2 public boolean wordBreak(String s, List<String> wordDict) { 3 while(!s.equals("")){ 4 boolean flag = false; 5 for(String word : wordDict){ 6 int i = KMP(s,word); 7 int j = 0; 8 if(i != -1){ 9 flag = true; 10 j = i + word.length() - 1; 11 s = s.substring(0,i)+s.substring(j+1,s.length()); 12 break; 13 } 14 } 15 if(!flag) 16 return false; 17 } 18 return true; 19 } 20 21 /* 22 * KMP算法的实现 23 */ 24 public int KMP(String str,String patten){ 25 char[] s = str.toCharArray(); 26 char[] ps = patten.toCharArray(); 27 int i = 0; 28 int j = 0; 29 int[] next = getNext(patten); 30 while(i < s.length && j < ps.length){ 31 if(j == -1 || s[i] == ps[j]){ 32 i++; 33 j++; 34 }else{ 35 j = next[j]; 36 } 37 } 38 if( j == ps.length) 39 return i-j; 40 else 41 return -1; 42 } 43 /* 44 * 获取next[]数组(KMP算法里很关键的一部分 45 */ 46 public int[] getNext(String patten){ 47 char[] p = patten.toCharArray(); 48 int[] next = new int[patten.length()]; 49 next[0] = -1; 50 int k = -1; 51 for(int i = 1; i < p.length-1; i++){ 52 if(k == -1 || p[i] == p[k]) { 53 next[++i] = ++k; 54 }else 55 k = next[k]; 56 } 57 return next; 58 } 59 }
示例中的三个都通过了验证,但是这个解法是错误的。以以下这个例子为例
输入:"cars" ["car","ca","rs"] 输出:false 预期:true
为什么会出现这个错误呢?这是因为在代码中,我是用forEach遍历wordDict中的每个单词,由于wordDict中的"car"被遍历到,"car"又是"cars"的子串,"cars"就被拆分成"car"和"s"的组合,但是wordDict中没有"s",因此程序判定"cars"不能由wordDict中的单词组成,返回false。但实际上,"cars"可以由"ca"和"rs"组成,程序没有考虑到这个,因此出错。也就是说,以上的程序没有考虑到一个字符串可能有多种组合。
介绍下官方题解中的使用宽度优先搜索来解这道题。
官方题解的这个方法和我的方法的区别在于,它在搜索到"cars"不能由"car"和"s"组成后,并没有结束程序,而是继续搜索wordDict中是否还有其它组合。
官方题解的代码如下:
1 public boolean wordBreak(String s, List<String> wordDict) { 2 Set<String> wordDictSet=new HashSet(wordDict); 3 Queue<Integer> queue = new LinkedList<>(); 4 int[] visited = new int[s.length()]; 5 queue.add(0); 6 while (!queue.isEmpty()) { 7 int start = queue.remove(); 8 if (visited[start] == 0) { 9 for (int end = start + 1; end <= s.length(); end++) { 10 if (wordDictSet.contains(s.substring(start, end))) { 11 queue.add(end); 12 if (end == s.length()) { 13 return true; 14 } 15 } 16 } 17 visited[start] = 1; 18 } 19 } 20 return false; 21 } 22 23 作者:LeetCode 24 链接:https://leetcode-cn.com/problems/word-break/solution/dan-ci-chai-fen-by-leetcode/ 25 来源:力扣(LeetCode) 26 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
官方的这个代码有个优化的空间,就是将visited的类型由int改为boolean,这样可以省部分空间。