算法实验室-29-回文分割问题[记忆递归][失败]

原题

检测回文

 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去讲吧。

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值