动态规划
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Main main = new Main();
List<String> tt = new ArrayList<>();
tt.add("cat");
tt.add("cats");
tt.add("and");
tt.add("dog");
tt.add("sand");
main.wordBreak("catsanddog", tt);
}
public List<String> wordBreak(String s, List<String> wordDict) {
if (wordDict.contains(s)) {
List<String> temp = new ArrayList<>();
temp.add(s);
return temp;
}
Map<Integer, List<String>> dpTable = new HashMap<>();
for (int i = 0; i < s.length(); i ++) {
for (int j = 0; j <= i; j ++) {
String temp = s.substring(j , i + 1);
if (wordDict.contains(temp) && (j - 1 < 0 || dpTable.containsKey(j - 1))) {
List<String> tempList;
if (dpTable.containsKey(j - 1)) {
tempList = dpTable.get(j - 1);
tempList = tempList.stream()
.map(ss -> ss + " " + temp)
.collect(Collectors.toList());
} else {
tempList = new ArrayList<>();
tempList.add(temp);
}
if (dpTable.containsKey(i)) {
tempList.addAll(dpTable.get(i));
}
dpTable.put(i, tempList);
}
}
}
if (dpTable.get(s.length() - 1) == null) {
return new ArrayList<>();
}
return dpTable.get(s.length() - 1);
}
}
基本思路
- 在单词拆分1的基础上, 利用一个map保存每个位置上可能的结果, 每次的结果需要将之前的结果进行拼接得到即可
缺点
- 内存超限了
动态规划2
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Main main = new Main();
List<String> tt = new ArrayList<>();
tt.add("cat");
tt.add("cats");
tt.add("and");
tt.add("dog");
tt.add("sand");
main.wordBreak("catsanddog", tt);
}
public List<String> wordBreak(String s, List<String> wordDict) {
if (wordDict.contains(s)) {
List<String> temp = new ArrayList<>();
temp.add(s);
return temp;
}
boolean [] dp = new boolean[s.length()];
for (int i = 0; i < s.length(); i ++) {
for (int j = 0; j <= i; j ++) {
String temp = s.substring(j , i + 1);
if (wordDict.contains(temp) && (j - 1 < 0 || dp[j - 1])) {
dp[i] = Boolean.TRUE;
break;
}
}
}
if (!dp[s.length() - 1]) {
return new ArrayList<>();
}
Map<Integer, List<String>> dpTable = new HashMap<>();
for (int i = 0; i < s.length(); i ++) {
for (int j = 0; j <= i; j ++) {
String temp = s.substring(j , i + 1);
if (wordDict.contains(temp) && (j - 1 < 0 || dpTable.containsKey(j - 1))) {
List<String> tempList;
if (dpTable.containsKey(j - 1)) {
tempList = dpTable.get(j - 1);
tempList = tempList.stream()
.map(ss -> ss + " " + temp)
.collect(Collectors.toList());
} else {
tempList = new ArrayList<>();
tempList.add(temp);
}
if (dpTable.containsKey(i)) {
tempList.addAll(dpTable.get(i));
}
dpTable.put(i, tempList);
}
}
}
if (dpTable.get(s.length() - 1) == null) {
return new ArrayList<>();
}
return dpTable.get(s.length() - 1);
}
}
基本思路
- 在1的基础上首先进行一轮动态规划, 判断整个字符串是否能够被拆分, 如果能, 再进行拆分, 避免了内存不足的问题
回溯1
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Main main = new Main();
List<String> tt = new ArrayList<>();
tt.add("cat");
tt.add("cats");
tt.add("and");
tt.add("dog");
tt.add("sand");
main.wordBreak("catsanddog", tt);
}
String s;
List<String> wordDict;
List<List<String>> ans = new ArrayList<>();
public List<String> wordBreak(String s, List<String> wordDict) {
this.s = s;
this.wordDict = wordDict;
digui(0, new ArrayList<>());
List<String> result = new ArrayList<>();
for (List<String> temp : ans) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < temp.size(); i ++) {
if (i == 0) {
sb.append(temp.get(i));
} else {
sb.append(" " + temp.get(i));
}
}
result.add(sb.toString());
}
return result;
}
public void digui(int start, List<String> path) {
if (start == s.length()) {
ans.add(new ArrayList<>(path));
return;
}
for (int i = start; i < s.length(); i ++) {
String temp = s.substring(start, i + 1);
if (wordDict.contains(temp)) {
// 作出选择
path.add(temp);
digui(i + 1, path);
// 撤销选择
if (path.size() > 0) {
path.remove(path.size() - 1);
}
}
}
}
}
基本思路
-
如果是寻找所有解的情况,那么用递归一定可以实现的
-
用递归的思想, 从字符串头部开始寻找字典中的单词, 寻找到就递归一次, 继续寻找之后字符串中的单词, 记录寻找到的结果
缺点
- 暴力递归, 没有做记忆的部分, 递归了很多重复的部分, 需要剪枝, 减少一些没有必要的递归
回溯2
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Main main = new Main();
List<String> tt = new ArrayList<>();
tt.add("cat");
tt.add("cats");
tt.add("and");
tt.add("dog");
tt.add("sand");
main.wordBreak("catsanddog", tt);
}
String s;
List<String> wordDict;
List<List<String>> ans = new ArrayList<>();
Map<Integer, List<List<String>>> tmepPathMap = new HashMap<>();
public List<String> wordBreak(String s, List<String> wordDict) {
this.s = s;
this.wordDict = wordDict;
digui(0);
List<String> result = new ArrayList<>();
for (List<String> temp : tmepPathMap.get(0)) {
StringBuilder sb = new StringBuilder();
for (int i = temp.size() - 1; i >= 0; i --) {
if (i == temp.size() - 1) {
sb.append(temp.get(i));
} else {
sb.append(" " + temp.get(i));
}
}
result.add(sb.toString());
}
return result;
}
public List<List<String>> digui(int start) {
if (!tmepPathMap.containsKey(start)) {
// 当前问题的结果
List<List<String>> nowStartList = new ArrayList<>();
if (start == s.length()) {
// 通过添加空list的方式, 保证递归返回的tempList如果有值, 就是正常的
// 否则返回的就是空
nowStartList.add(new ArrayList<>());
}
for (int i = start; i < s.length(); i ++) {
String temp = s.substring(start, i + 1);
if (wordDict.contains(temp)) {
List<List<String>> tempList = digui(i + 1);
for (List<String> ttttt: tempList) {
List<String> cloneList= new ArrayList<>(ttttt);
cloneList.add(temp);
nowStartList.add(cloneList);
}
}
}
tmepPathMap.put(start, nowStartList);
}
return tmepPathMap.get(start);
}
}
基本思路
-
主要思路是递归, 然后想办法用一个map来记录j....n-1所有肯能的单词表的情况, 避免重复计算的问题
-
为了记忆, 所以递归的返回值只能是单个子问题的答案, 即只能是List<List>
-
如果字符串的最后构成了单词, 那么返回一个空list, 用来表示次此次递归到底的结论是有效的. 否则返回null, 这样就不会记录, 这一点很重要