序
昨天晚上做到了一道关于字符串分割子串的问题,当时找规律没做出来,确实规律太复杂了,试着用回溯法来解。目前测试正常。
问题重述
输入是一个字符串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;
}