LeetCode链接:https://leetcode-cn.com/problems/word-break/
题目:
给定一个非空字符串s和一个包含非空单词列表的字典wordDict,判定s是否可以被空格拆分为一个或多个在字典中出现的单词;
注:
1、拆分时可以重复使用字典中的单词;
2、可以假设字典中没有重复的单词;
我的想法是使用回溯法,逐个查找s中可以在字典wordDict中匹配的单词
1 import java.util.*; 2 3 public class Solution { 4 private List<String> wordDict; 5 6 public boolean wordBreak(String s, List<String> wordDict) { 7 this.wordDict = wordDict; 8 return find(s, 0); 9 } 10 11 public boolean find(String s, int i) 12 { 13 if(i == s.length()) 14 { 15 return true; 16 } 17 18 for(int j = i; j < s.length(); j++) // 步骤2,如果步骤1中的后续匹配失败,则回退并将指针j后移,查找字典中是否包含其它子串 19 { 20 if(wordDict.contains(s.substring(i, j+1))) 21 { 22 if(find(s, j+1)) //步骤1,查找到在字典中的单词时,就继续进行后续匹配 23 { 24 return true; 25 } 26 } 27 } 28 return false; 29 } 30 }
上述算法的时间复杂度,在最好的情况下时间复杂度为O(n),即只需遍历一次字符串,但在最坏的情况下时间复杂度为O(n^n)。在leetcode的36个测试用例里通过了29个测试用例,提示说在某些测试用例上超时,如:s="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab",wordDict=["a","aa","aaa","aaaa","aaaaa","aaaaaa","aaaaaaa","aaaaaaaa","aaaaaaaaa","aaaaaaaaaa"],在这个测试用例上,上述算法会存在大量的重复比较开销,有什么方法能避免重复比较呢?
通过使用一个数组跟踪子串的匹配状态来避免重复比较,官方提供的三个时间复杂度为O(n^2),空间复杂度为O(n)的方法,看这里
1 import java.util.*; 2 3 public class Solution { 4 private List<String> wordDict; 5 6 private int[] memory; //memory数组记录子串的匹配状态 7 8 public boolean wordBreak(String s, List<String> wordDict) { 9 this.wordDict = wordDict; 10 this.memory = new int[s.length()]; 11 return find(s, 0); 12 } 13 14 public boolean find(String s, int i) 15 { 16 if(i == s.length()) 17 { 18 return true; 19 } 20 if(memory[i] == 1) //memory[i]==1时,表明以i为起始的子串无法匹配 21 { 22 return false; 23 } 24 for(int j = i; j < s.length(); j++) 25 { 26 if(wordDict.contains(s.substring(i, j+1)) && find(s, j+1)) 27 { 28 return true; 29 } 30 } 31 memory[i] = 1; 32 return false; 33 } 34 }
方法二:
1 import java.util.*; 2 3 public class Solution { 4 public boolean wordBreak(String s, List<String> wordDict) { 5 int[] visited = new int[s.length()]; 6 Queue<Integer> queue = new LinkedList<>(); 7 queue.add(0); 8 while(!queue.isEmpty()) { 9 int start = queue.remove(); 10 if(visited[start] == 0) { 11 for(int j = start+1; j <= s.length(); j++) { 12 if(wordDict.contains(s.substring(start, j))) { 13 queue.add(j); //表明[0, j)已经在字典中找到匹配,下一次从j位置开始寻找 14 if(j == s.length()) { //如果s已经全部匹配完毕,则返回true 15 return true; 16 } 17 } 18 } 19 visited[start] = 1; //visited[start]==1,表示子串s.substring(start, s.length())在字典中没有找到匹配 20 } 21 } 22 return false; 23 } 24 }
方法三:动态规划
1 import java.util.*; 2 3 public class Solution { 4 public boolean wordBreak(String s, List<String> wordDict) { 5 boolean[] dp = new boolean[s.length() + 1]; //dp[i]表示子串s.substring(0, i)已在字典中找到匹配 6 dp[0] = true; 7 for(int i = 1; i <= s.length(); i++) { 8 for(int j = 0; j < i; j++) { 9 if(dp[j] && wordDict.contains(s.substring(j, i))) { //如果dp[j]==false,说明s.substring(0,j)在字典中没有匹配的字符串,则就没有必要测试后续子串s.substring(j, i) 10 dp[i] = true; //因为必须前面的匹配好了,才有进行后续匹配的必要 11 break; 12 } 13 } 14 } 15 return dp[s.length()]; //如果dp[s.length()]==true,表明sub.substring(0, s.length())已经全部在字典中找到匹配 16 } 17 }