Leetcode140.单词拆分Word-break-ii
题目:
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子
思路:
1.尾部追加法
我想到了要用递归去做,但是我的想法是从头递归!此处从尾递归效率更高。除此之外,我的想法不是很能处理ArrayList的追加操作。我一开始的想法是,构造一个stringbuffer的变量,不断地追加被分割出来的单词,然后递归。
自己的思路错误在于:
- 没有想到最后一个字符的处理方式,可以通sb.deleteCharAt(sb.length()-1)解决最后一个空格问题。
- 如果用sb进行追加(代码中使用temp的地方)操作,不能解决回调问题(因为返回上层递归需要删除本次递归中追加的字符,具体的个数不好提取,但是也是可以知道的,只不过这张做法比较麻烦)。也就是说,加入遇到 catsanddogs
那么cat sand dog和 cats and dog两者都可能.我一开始的想法是:递归判断完dict是否包含剩余字符串中的字符之后,令sb=null,然后重新添加sb.subString,问题在于,sb=null之后,前面保存的内容也将不存在,所以最后res追加只可能为空。
代码1:
ArrayList<String> res = new ArrayList<>();
ArrayList<String> temp = new ArrayList<>();
public ArrayList<String> wordBreak(String s, Set<String> dict) {
dfs(s,dict);
return res;
}
private void dfs(String s ,Set<String> dict) {
if(s.length()<1) {
StringBuffer sb = new StringBuffer();
for(String str : temp)
sb.append(str).append(" "); //按照substring分成多个单词
sb.deleteCharAt(sb.length()-1);
res.add(sb.toString());
}
for(int i=s.length()-1;i>=0;--i) {
String str = s.substring(i,s.length());//从i开始一直到最后一个字符都包含在内,则加入temp
if(dict.contains(str)) {
temp.add(0, str); //从头开始添加,所以需要从尾遍历(如果从头遍历的话,每次递归remove的应该是temp中最后一个元素)
dfs(s.substring(0, i),dict);
//回到dfs(s.substring(0, i),dict)操作之前的状态,因为每次只递归追加一个str,删除这个插入的str回到上层递归
temp.remove(0);
}
}
}
思路2:
递归时间太长,采用map存储可能的情况(好好研究,这种方法,可以适合各种顺序的输出)
该思路是从头开始遍历,并且从dict的角度出发进行思考。
使用一个map存放:key对应以某个单词开头直至结尾的str,value为其对应分割后的结果
比如:
key | value |
---|---|
dog | dog |
sanddog | sand dog |
catsanddog | cat sand dog |
结束以cat开头的一次遍历(for(substr:dict))将整个s:catsanddog放入map中并return最终的res
开始以cats的第二次遍历
key | value |
---|---|
dog | dog |
sanddog | sand dog |
catsanddog | cat sand dog,cats and dog(应该继续追加的,但是怎么追加还没搞清楚) |
anddog | and dog |
(此时map中已经保留了dog那么,dog对应的res可以直接get,只需要追加两个即可)
代码2:
public ArrayList<String> wordBreak(String s, Set<String> dict) {
return DFS(s,dict,new HashMap<String,ArrayList<String>>());
}
private ArrayList<String> DFS(String s, Set<String> dict,HashMap<String,ArrayList<String>> map){
if(map.containsKey(s))
return map.get(s);
ArrayList<String> res = new ArrayList<>();
if(s.length()<1) {
res.add("");
return res;
}
for(String subStr : dict) {
if(s.startsWith(subStr )) {//但凡字符串s中有以字典中subStr开头的字符串,那么将subStr加入res
for(String str : DFS(s.substring(subStr.length()), dict, map)) //从subStr这个词结束向后递归
res.add(subStr+(str==""?"":" ")+str);//str==""?"":" "的意思是,如果有字符串就加空格,没有字符串了就不加。
}
}
map.put(s, res);
return res;
}
LeetCode131分割回文串palindrome-partitioning
题目:
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。返回 s 所有可能的分割方案。
思路:
递归调用。重点在于,helper中需要传递一个new ArrayList temp,用以保留上一轮结果。
Temp的初始化,用上一轮结果list初始化!否则,每一轮的temp都是只包含本轮结果的值!(我自己写的错在这)
注意这种返回值是List<List>类型的问题
DFS当打中的 ArrayList temp = new ArrayList<>(list);
注意:
-
新建一个ArrayList。为什么不直接用list的原因在于结果错误!!原因未知,研究研究
- 如果只用唯一的list传递,那么所有的追加操作都将在list之后进行
- 那么每次调用完dfs下一次递归后,应该list.remove新插入的字符串,返回上一级(删除操作错误,返回空)
-
将上次递归产生的结果list作为参数传入新建的temp中,以保证追加操作在上一步完成后继续
代码:
ArrayList<ArrayList<String>> res = new ArrayList<ArrayList<String>> ();
public ArrayList<ArrayList<String>> partition(String s) {
dfs(s, new ArrayList<String>());
return res;
}
private void dfs(String s,ArrayList<String> list) {
if(s.length()<1) { //如果已经没有字符串可以分割了,那么就将list加入res中,然后返回res;
res.add(list);
return;
}
for(int i = 0;i<s.length();++i) {
ArrayList<String> temp = new ArrayList<>(list); //作为一条记录返回。
if(check(s.substring(0,i+1))) {
temp.add(s.substring(0,i+1));
//在i相同的情况下该条记录的temp继续传入后期的记录
dfs(s.substring(i+1),temp);
}
}
}
private boolean check(String s) {
int i=0,j=s.length()-1;
while(j>i) {
if(s.charAt(i)==s.charAt(j)) {
++i;
--j;
}else
return false;
}
return true;
}
leetCode 132. 分割回文串②
题目:
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。
思路1:动态规划
代码1:
public int minCut(String s) {
int[][] dp = new int[s.length()][s.length()];
int[] cut = new int[s.length()+1];
for(int i = s.length()-1;i>=0;--i) {
cut[i]=Integer.MAX_VALUE;
for(int j =i;j<s.length();j++) {
if(s.charAt(i)==s.charAt(j)&&(j-i<=1 || dp[i+1][j-1]==1)) {
dp[i][j]=1;
cut[i]=Math.min(1+cut[j+1], cut[i]);
}
}
}
return cut[0]-1;
}
思路2:暴力求解
构建一个一维数组DP[i],里面存放的是:从0,位置i上的字符,需要分割的次数
关键点在于:dp[i-1]+1=dp[i]
代码2:
private bool isPalindrome(String s){//判断是否回文
int first=0;
int end=s.size()-1;
while(first<end) {//此处不能等于,否则死循环
if(s[first++]!=s[end--])
return false;
}
return true;
}
public int minCut(String s) {
if(isPalindrome(s)) return 0;//首先进行整体判断,如果整体是个回文串则不需分割
int n=s.length();
int[] dp=new int[n];
dp[0]=0;
//考虑dp[i]的计算
//1.[0~i-1]不构成回文字符,但是[0-i]构成回文字符,dp[i]=0
//2.【0~i】不构成回文符,需要进行内部分割,遍历寻找分割点,得到分割次数最小的结果
for(int i=1;i<n;i++)
{
dp[i]=dp[i-1]+1;//i位置的字符可能于前i-1个不构成回文串,所以需要在dp[i-1]的基础上+1
if(isPalindrome(s.substr(0,i+1)))//如果[0,i]构成回文串,那么dp[i]=0,即不需要分割
dp[i]=0;
else
for(int j=1;j<i;j++)
{
if(isPalindrome(s.substr(j,i-j+1)))//在[0,i]中判断需要分割几次
dp[i]=((dp[j-1]+1)<dp[i])?(dp[j-1]+1):dp[i];//如果在[0,i]中分割之后得到的结果比dp[i-1]+1小(或者是上一个j得到的结果小,那么更新dp[i])
}
}
return dp[n-1];
}