https://oj.leetcode.com/problems/word-break-ii/
Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word.
Return all such possible sentences.
For example, given
s = "catsanddog"
,
dict = ["cat", "cats", "and", "sand", "dog"]
.
A solution is ["cats and dog", "cat sand dog"]
.
这道题目与上题看似相似,其实只多一步还是有很大的区别。首先想到可以借助上题的思路,首先判断s是不是可以word break,并在这个过程中生成一个数组,每个数组是一个ArrayList,divideList[m].contains(n)就表示s.substring(m.n)在dict中。
考虑上面的例子。divideList的形式如下:
0——3,4
3——7
4——7
7——10
这是不是就是图的邻接表表示法,当然用邻接矩阵(二维数组)也是同样可以的。就是这个问题,困了我两天,期间还忙了很多事情。还是基本功不扎实。
于是剩下的问题,就是从0到10的所有可能路径。推广,找出所有可能的word break,就是找出divideList[]中所有从0到length - 1的路径。这就是图中的DFS或者BFS,可以用递归或者迭代来做。
public class Solution { public List<String> wordBreak(String s, Set<String> dict) { List<String> list = new ArrayList<String>(); boolean[] dp = new boolean[s.length()]; //一个邻接表表示的图,divideList[m] = n表示s的m到n的子串在dict中 //找出所有可能的word break,就是找出所有从0到length - 1的路径 List[] divideList = new ArrayList[s.length()]; if (s.length() == 0) { return list; } if (dict.contains(s.substring(0, 1))) { dp[0] = true; divideList[0] = new ArrayList<Integer>(); divideList[0].add(1); } for (int i = 1; i < s.length(); i++) { divideList[i] = new ArrayList<Integer>(); if (dict.contains(s.substring(0, i + 1))) { if (divideList[0] == null) { divideList[0] = new ArrayList<Integer>(); } divideList[0].add(i + 1); } for (int j = 0; j < i; j++) { if (dict.contains(s.substring(0, i + 1)) || (dp[j] == true && dict.contains(s.substring(j + 1, i + 1)))) { dp[i] = true; // divideList[i].add(j); } if (dp[j] == true && dict.contains(s.substring(j + 1, i + 1))) { divideList[j + 1].add(i + 1); } } } if (dp[s.length() - 1] == false) { return list; } //dfs divideList StringBuffer bf = new StringBuffer(); dfsStringList(s, 0, list, bf, divideList); return list; } public static void dfsStringList(String s, int start, List<String> list, StringBuffer bf, List[] divideList) { //递归结束条件,到达string的结尾 if (start == s.length()) { list.add(new String(bf)); return; } //根据divideList的定义,如果调用到这个方法,必然从divideList[0]开始 for (int i = 0; i < divideList[start].size(); i++) { int lengthBeforeAppend = bf.length(); int end = (Integer) divideList[start].get(i); //每个单词后加上空格 if(lengthBeforeAppend != 0){ bf = bf.append(" "); } //将一个分词加入bf bf = bf.append(s.substring(start, end)); //对这个分词后的子串递归dfs dfsStringList(s, end, list, bf, divideList); //考虑一个例子,0——3,4;3——7;4——7;7——10。0-3结束后递归3-10 //递归结束后list.add(new String(bf));然后弹出栈,删除bf中7-10的子串,i向后,因为此时i只有一个,所以直到0,i到1,处理0-4 bf = bf.delete(lengthBeforeAppend, bf.length()); } } }
事实上,这道题,dp只用来判断是否可以word break就可以了。如果dp[s.length() - 1] == fals,直接return 空list。后面索性将dict传入dfs方法,直接做dfs,反而较为简便。问题是,没有剪枝的过程,和上述方法相比,多了不少计算。下面是网上搜索的代码,下面会说明出处。
public class Solution { public ArrayList<String> wordBreak(String s, Set<String> dict) { ArrayList<String> ret = new ArrayList<String>(); if (s==null || s.length()==0) return ret; int n = s.length(); boolean[] dp = new boolean[n+1]; dp[0] = true; for (int i=1; i<=n; i++) { if (dict.contains(s.substring(0, i))) { dp[i] = true; continue; } for (int j=0; j<i; j++) { if (dp[j] && dict.contains(s.substring(j, i))) { dp[i] = true; } } } if (dp[n] == false) return ret; //DP的作用就这一行!!! StringBuilder cur = new StringBuilder(); dfs(s, 0, cur, ret, dict); return ret; } public void dfs(String s, int start, StringBuilder cur, ArrayList<String> ret, Set<String> dict) { int n = s.length(); if (start >= n) { ret.add(new String(cur)); return; } for (int i=start+1; i<=n; i++) { String sub = s.substring(start, i); if (dict.contains(sub)) { int oldLen = cur.length(); if (oldLen!=0) cur.append(" "); cur.append(sub); dfs(s, i, cur, ret, dict); cur.delete(oldLen, cur.length()); } } } }
参考地址:
https://stupidcodergoodluck.wordpress.com/2013/11/16/leetcode-word-break-ii/
http://www.cnblogs.com/feiling/p/3357067.html
http://www.acmerblog.com/word-break-ii-6128.html
http://fisherlei.blogspot.jp/2013/11/leetcode-wordbreak-ii-solution.html