目录
首先想到的解法——暴力枚举
package LeetCode.DP; import java.util.HashSet; import java.util.List; public class Test139 { public boolean wordBreak(String s, List<String> wordDict) { HashSet<String> hashSet=new HashSet<>(); for (String str : wordDict) { hashSet.add(str); } return getResult(s,hashSet,0); } //这个递归函数的语义就是从Sting从start开始的子字符串是否能由wordDict中的单词组成 private boolean getResult(String s, HashSet<String> Set, int start) { if(start==s.length()){ //说明已经将这个字符串遍历完了 return true; } for (int i = start; i <s.length() ; i++) { //substring是左闭右开 if(Set.contains(s.substring(start,i+1))&&getResult(s,Set,i+1)){ return true; } } return false; } }
存在的问题
- 我们发现这样会存在这大量的重复运算,解决的方法就是剪枝和自底向上
记忆化剪枝
用HashMap记录
class Solution { public boolean wordBreak(String s, List<String> wordDict) { HashSet<String> hashSet=new HashSet<>(); for (String str : wordDict) { hashSet.add(str); } HashMap<Integer,Boolean> map=new HashMap<>(); //用来存储以start开头的子字符串是否能用字段的单词组成 return getResult(s,hashSet,0,map); } //这个递归函数的语义就是从Sting从start开始的子字符串是否能由wordDict中的单词组成 private boolean getResult(String s, HashSet<String> Set, int start,HashMap<Integer,Boolean>map) { if(start==s.length()){ //说明已经将这个字符串遍历完了 return true; } if (map.containsKey(start)){ return map.get(start); } for (int i = start; i <s.length() ; i++) { //substring是左闭右开 if(Set.contains(s.substring(start,i+1))&&getResult(s,Set,i+1,map)){ map.put(start,true); return true; } } map.put(start,false); return false; }
HashMap记录的优化
public boolean wordBreak(String s, List<String> wordDict) { HashSet<String> set = new HashSet<>(); int max = Integer.MIN_VALUE; for (String str : wordDict) { max = Math.max(str.length(),max); set.add(str); } // 记录map,记录重复的计算的结果 HashMap<Integer,Boolean> map = new HashMap<>(); return getResult(s,0,set,max,map); } /** * * @param s * @param start 表示索引开始位置 * @param set 字段中字符串的集合 * @param max 每次遍历s时不可以超出max长度,否则肯定为false * @param map 记忆表 * @return */ private boolean getResult(String s, int start, HashSet<String> set,int max,HashMap<Integer,Boolean> map) { if(start == s.length()){ return true; } if(map.containsKey(start)){ return map.get(start); } // 关键步骤: 遍历的时候加上max的长度限制,避免无限遍历至s结尾 for (int i = start; i < start + max && i < s.length(); i++) { if(set.contains(s.substring(start,i + 1))){ if(getResult(s,i+1,set,max,map)){ map.put(start,true); return true; } } } map.put(start,false); return false; }
- 我们记录下字典中单词最长的单词长度max,每次从start开始去找一个匹配的单词,如果匹配的长度都大于max,后面再怎么匹配,都不可能匹配成功
用dp数组记录
package LeetCode.DP; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; public class Test139 { public boolean wordBreak(String s, List<String> wordDict) { HashSet<String> hashSet=new HashSet<>(); int max = Integer.MIN_VALUE; for (String str : wordDict) { hashSet.add(str); max = Math.max(str.length(),max); } // HashMap<Integer,Boolean> map=new HashMap<>(); // //用来存储以start开头的子字符串是否能用字段的单词组成 int dp[]=new int[s.length()+1]; Arrays.fill(dp,0); return getResult(s,hashSet,0,dp,max); } //这个递归函数的语义就是从Sting从start开始的子字符串是否能由wordDict中的单词组成 private boolean getResult(String s, HashSet<String> Set, int start,int dp[],int max) { if(start==s.length()){ //说明已经将这个字符串遍历完了 return true; } if (dp[start]==1){ return true; } if(dp[start]==-1){ return false; } for (int i = start; i <s.length()&&i < start + max ; i++) { //substring是左闭右开 if(Set.contains(s.substring(start,i+1))&&getResult(s,Set,i+1,dp,max)){ dp[i]=1; return true; } } dp[start]=-1; return false; } }
自底向上——动态规划
class Solution { public boolean wordBreak(String s, List<String> wordDict) { HashSet<String> set =new HashSet<>(wordDict); boolean dp[]=new boolean[s.length()+1]; //base case dp[0]=true; for (int i = 1; i <=s.length(); i++) { for (int j = i-1; j >=0 ; j--) { //substring是左闭右开 //状态转换方程 if(dp[j]&&set.contains(s.substring(j,i))){ dp[i]=true; break; } } } return dp[s.length()]; } }