检测回文
public boolean detectReText(String val){
boolean match = true;
for (int i =0;i<val.length()/2;i++){
if(val.charAt(i)!=val.charAt(val.length()-i-1)){
match = false;
break;
}
}
return match;
}
如何将aab拆分成[a,a,b]和[aa,b]两种回文形式
这个拆法显然和之前做的单词分割的拆法又有些细微的差别,我们依旧通过递归来进行拆解,但是我们需要通过链表进行数据记录
public void split(String data,int depth){
//递归结束条件
if(data.length()==0){
return;
}
for (int i =1;i<=data.length();i++){
String singleWord = data.substring(0,i);
Debug.Log(singleWord,data,depth);
split(data.substring(singleWord.length()),depth+1);
}
}
这个是输出的结果:
[a] [aab] [0]
[a] [ab] [1]
[b] [b] [2]
[ab] [ab] [1]
[aa] [aab] [0]
[b] [b] [1]
[aab] [aab] [0]
绘制出一张图来看:
剪枝
然后就是剪枝条件:判断是否为回文,如果是回文进行下一步递归
public void split(String data,int depth){
//递归结束条件
if(data.length()==0){
return;
}
for (int i =1;i<=data.length();i++){
String singleWord = data.substring(0,i);
if(detectReText(singleWord)){
Debug.Log(singleWord,data,depth);
split(data.substring(singleWord.length()),depth+1);
}
}
}
[a] [aab] [0]
[a] [ab] [1]
[b] [b] [2]
[aa] [aab] [0]
[b] [b] [1]
深度锁定
接下来,就可以通过判定是否触及最后一个回文,从而其深度就是拆分次数
public void split(String data,int depth){
//递归结束条件
if(data.length()==0){
Debug.Log("深度值:",depth-1);
return;
}
for (int i =1;i<=data.length();i++){
String singleWord = data.substring(0,i);
if(detectReText(singleWord)){
// Debug.Log(singleWord,data,depth);
split(data.substring(singleWord.length()),depth+1);
}
}
}
输出:
[深度值:] [2]
[深度值:] [1]
这个时候,选择最小的深度值作为输出即可。
but....
这道题的难点不是实现这样一个功能,而是,时间复杂度
上述办法的时间复杂度实在是太高了:达到了o(n!)
你直接丢进去算是直接就不给过的:
public List<Integer> list;
/**
* 检测回文
* @param val
* @return
*/
public boolean detectGreed(String val){
boolean match = true;
for (int i =0;i<val.length()/2;i++){
if(val.charAt(i)!=val.charAt(val.length()-i-1)){
match = false;
break;
}
}
return match;
}
public void split(String val,int depth){
//如果正向搜时间复杂度过高,那就反向搜
int max = 0;
for (int i =0;i<val.length();i++){
String singleWord = val.substring(0,i+1);
if(detectGreed(singleWord)){
split(val.substring(singleWord.length()),depth+1);
//检索到最后一个
if (singleWord.length() == val.length()){
list.add(depth);
}
}
}
}
public int context(String val){
list = new ArrayList<Integer>();
split(val,0);
int max =list.get(0);
for (Integer i: list){
if(max>i){
max = i;
}
}
return max;
}
这是我投的第一版代码,就为了这破事,我愁了一天
之前拆单词的为什么能过呢?
可以看到的是:
这张图的步骤(蓝色)1->2和4->5是一个重复计算的过程,上一个单词拆解的也是这样搞
它用一个map记住了b对应的拆解可能,然后遇到b的时候,就直接将b的拆解结果返回给步骤4作为结果
这样步骤4就不用重复计算步骤5,从而省下了时间。
我也是这样干的呀...
记忆递归存在的问题分析
但是,这种记忆递归,本身存在一定的问题,我复盘了很久,将问题归结出来
首先,记忆递归的数据结构是一个HashMap<String,List<String>> map
它的键值就是当前拆分字符串,它的值就是拆分结果,那么一个拆分字符串和其对应的结果应该是1对1的关系
但是...你复盘了一下,你会发现如果aabb中的abb作为键,那么其拆分结果很可能是两个List<String>
1.a b b
2.a bb
现在问题就很严重了,因为这样做的话,那么根本无法记录住重复的结果集
为什么单词拆分可以通过递归记忆进行优化?
我仔细复盘了单词拆分的过程,对于nowcoderisbest这个案例
当以coderisbest作为拆分时,可能拆出
1.coder is best
2.coderis best
两种情况,它没有绕开!它就直接将它存到List<String>里面
也就是说,此时coderisbest可以映射为:
当遇到重复的拆解coderisbest时,直接获取其拆解结果集就可以了,从而免于重复劳动
从拆解单词中获取的灵感与代码的新优化
复盘了两种算法的方式,我们现在可以尝试通过之前的这种拆解灵感去降低复杂度,最后,我们只需要判断谁的空格少,谁就是爸爸
public List<String> split2(String data,int depth,HashMap<String,List<String>> map){
if(map.containsKey(data)){
return map.get(data);
}
List<String> list = new ArrayList<String>();
//递归结束条件
if(data.length()==0){
list.add("");
return list;
}
for (int i =1;i<=data.length();i++){
String singleWord = data.substring(0,i);
if(detectReText(singleWord)){
List<String> ans = split2(data.substring(singleWord.length()),depth+1,map);
for (String key:ans
) {
list.add((singleWord+" "+key).trim());
}
}
}
map.put(data,list);
return list;
}
对于aab来讲:
对于aaab来讲:
对于aaabb来讲:
a a a b b
a a a bb
a aa b b
a aa bb
aa a b b
aa a bb
aaa b b
aaa bb
只需要找到最短的空格就行:
List<String> list = t19.split2(data,0,new HashMap<String, List<String>>());
Debug.Log(list, Debug.DebugType.MultiRow);
int len = list.get(0).length();
for (String key: list){
if(len>key.length()){
len = key.length();
}
}
Debug.Log( len-data.length() );
拿过去试试:
import java.util.*;
public class Solution {
/**
*
* @param s string字符串
* @return int整型
*/
public int minCut (String s) {
// write code here
return context(s);
}
public List<Integer> list;
/**
* 检测回文
* @param val
* @return
*/
public boolean detectReText(String val){
boolean match = true;
for (int i =0;i<val.length()/2;i++){
if(val.charAt(i)!=val.charAt(val.length()-i-1)){
match = false;
break;
}
}
return match;
}
public int context(String val){
List<String> list = this.split2(val,0,new HashMap<String, List<String>>());
int len = list.get(0).length();
for (String key: list){
if(len>key.length()){
len = key.length();
}
}
return len-val.length() ;
}
public List<String> split2(String data,int depth,HashMap<String,List<String>> map){
if(map.containsKey(data)){
return map.get(data);
}
List<String> list = new ArrayList<String>();
//递归结束条件
if(data.length()==0){
list.add("");
return list;
}
for (int i =1;i<=data.length();i++){
String singleWord = data.substring(0,i);
if(detectReText(singleWord)){
List<String> ans = split2(data.substring(singleWord.length()),depth+1,map);
for (String key:ans
) {
list.add((singleWord+" "+key).trim());
}
}
}
map.put(data,list);
return list;
}
}
都已经这样了,还是不给我过,我都服了,
再次复盘
上一个为什么可以,主要是因为,单词的长度本身就给我们限定住了一次判定的len的大小,所以可以减低一些的时间费用
但是这次没给,如果实在想不到进一步的优化手段,那就只能的...通过其他手段去操弄了。
篇幅太长,我们放到30去讲吧。