字符串按照字典序的分割子串方法数量

昨天晚上做到了一道关于字符串分割子串的问题,当时找规律没做出来,确实规律太复杂了,试着用回溯法来解。目前测试正常。

问题重述

输入是一个字符串s,将其保序拆分变成子串集合v={s1,s2,...,sn},其中s=s1+s2+...+sn,使得集合内的子串字符串满足字典序增序排列(即s1<s2<...<sn),求有多少种拆分方法?这个问题还是有点咬文嚼字,下面是几个例子:

示例1:

input: abca
output: 4
explain: 一共有{a,b,ca}, {a,bca}, {ab,ca}, {abca}四种

示例2:

input: ppq
output: 3
explain: 一共有{p,pq}, {pp,q}, {ppq}三种
注:{p,p,q}不满足,因为p和p不满足增序

解决方法:

最开始是想着使用动态规划或者贪心来做,通过一个一个扫描找到规律等到一个公式,后来发现会出现许多特殊情况,无法做到完整解,不过这里面的一部分规律也为我们之后的解提供了辅助:

1 首先定义一个num变量表示总的分法,使用遍历扫描的方法进行计算并更改num的值,同时定义一个vector <string> logs的集合,来记录当前的字符串分割集合 。
2 比如当前扫描的字符为p,当p大于logs的最后一个字符串时,就可以num=num*2,理由是这个p只有两种做法,第一种是单独作为一个子串,加在每一个分割之后,此时满足增序条件;第二种是将这个p加在最后一个字符串上,此时也满足增序条件。
3 但是如果是小于等于呢?这里比较复杂,有兴趣的可以自己用笔和纸画一画,我个人是没能形成一个普适且合理的规则。

于是,为了能够得到正确且完整的解,我们决定使用回溯法进行解题。算法思路和上面很像:
遍历扫描输入字符串,每个字符串形成树的一层,遇到新的字符有两种操作:一种是将新字符作为新的子串加在当前logs的后面,第二种是将这个字符与logs最后一项做字符串的连接操作。然后判断新的logs是否合理,以及是否有拓展的可能性,如果已经失败则直接剪枝。到达叶子节点后再判断是否满足增序,满足增序的结果加1,生成整个树之后,即得到完整解。

这里有一个需要注意的点,如果logs倒数第一个字符串和倒数第二个字符串和子串匹配的,则还有拓展的机会(比如{ppp,p}这种,将来的下一个字符可能是q,就可以生成{ppp,pq}的答案。但如果是{ppp,po},则直接剪枝即可,因为这个已经失败了。)

思路有了,就放代码了,有的地方写得可能繁琐了一些,还有很多优化空间,同时正在考虑动态规划的思路,写的时候发现有许多重合子问题,不过还没想好怎么实现动态规划。

#include <iostream>
#include <string>
#include <vector>

using namespace std;

vector<string> logs;
string input="checcvvcheckMiheccvvcheckMihecc";

//检测当前logs是否有拓展可能
bool checkMiddleInvalid()
{
    int curSize=logs.size()-1;
    
    //至少有3个string
    if(curSize>=2)
    {
        if(logs[curSize-2]>=logs[curSize-1])
            return false;
        if(logs[curSize]>=logs[curSize-1])
            return true;
        else
        {
            int size1=logs[curSize].size();
            int size0=logs[curSize-1].size();
            if(size0>size1)
            {
                for(int i=0;i<size1;i++)
                {
                    if(logs[0][i]>logs[1][i])
                        return false;
                }
                return true;
            }
            else
            {
                return false;
            }
        }        
    }

    //只有2个string
    if(curSize==1)
    {
        //满足>=肯定可以
        if(logs[1]>=logs[0])
            return true;
        //在<的情况下,还有一种拓展可能,就是比较子串
        else
        {
            int size1=logs[1].size();
            int size0=logs[0].size();
            if(size0>size1)
            {
                for(int i=0;i<size1;i++)
                {
                    if(logs[0][i]>logs[1][i])
                        return false;
                }
                return true;
            }
            else
            {
                return false;
            }
        }
    }    

    return true;
}

bool checkLeavesInvalid()
{
    int finSize=logs.size();
    for(int i=0;i<finSize;i++)
    {
        if(i+1<finSize)
        {
            if(logs[i]>=logs[i+1])
                return false;
        }
    }
    // for(int i=0;i<finSize;i++)
    //     cout<<logs[i]<<" ";
    // cout<<endl;
    return true;
}

//curIndex是指到input string的第几个字符了
int downExpand(int curIndex, int& length)
{
    int result=0;
    if(curIndex==length-1)
    {
        if(checkLeavesInvalid())
            result=1;
        else
            result =0;        
    }
    else
    {
        //中间还能拓展不?
        if(checkMiddleInvalid())
        {
            //向左拓展,将vector加1,把新字符放到后面
            string s;
            s.push_back(input[curIndex+1]);
            logs.push_back(s);
            result+=downExpand(curIndex+1,length);
            logs.pop_back();

            //向右拓展,将v新字符加到vector的最后一个字符串上
            string temp=logs[logs.size()-1];
            string temp1=temp+input[curIndex+1];
            logs[logs.size()-1]=temp1;
            result+=downExpand(curIndex+1,length);
            logs[logs.size()-1]=temp;
        }
        else
        {
            //如果中间已经确定拓展失败,则剪枝
            result=0;
        }
        
    }
    
    return result;
}


int main()
{
    int result=0;
    int length=input.size();

    string s;
    s.push_back(input[0]);
    logs.push_back(s);
    result+=downExpand(0,length);

    cout<<result<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值