Leetcode 97.交错字符串
题目简述
给定三个字符串s1,s2,s3,请帮忙验证s3是否是由s1和s2交错组成的
两个字符串s和t交错的定义如下:
s和t被分割成若干非空子字符串
- s = s1 + s2 + … + sm
- t = t1 + t2 + … + tn
- |n - m| <= 1
- 交错即 s1 + t1 + s2 + t2 + … 或 t1 + s1 + t2 + s2 + …
示例:
s1 = “aabcc”
s2 = “dbbca”
s3 = “aadbbcbcac”
我的超时解法:三指针遍历+回溯法
具体思路:
解法1.0:我创建三个指针,分别指向s1,s2,s3,然后开始遍历,比照s1和s3,字符相同则指针加1,不同则转去比照s2和s3,相同则指针前移,不同则切换至s1,循环往复。如果全部匹配成功,返回true,否则返回false
示例:
以上是我最开始的想法,其中存在一定漏洞
我的对照方法是每次移动指针到第一处不匹配的位置,但是字符串的切分未必就是这种模式。直观一点,举一个反例:s1=“aabcc”,s2=“dbbca”,s3=“aadbcbbcac”,按照我上面提出的解法最终结果为“false”,但是事实是有解的——true
我的解法:
事实存在交错解法:
因此,需要对解法作出修改
解法2.0:相比解法1.0,需要改进这几处
- s1/s2和s3进行字符对照时【以s1为例】
- s1[a] == s3[c] && s2[b] != s3[c]时,a++,c++
- s1[a] == s3[c] && s2[b] == s3[c]时,先尝试a++(选择s1的字符),如果最终结果为false,再回溯到这个节点,选择b++(选择s2的字符)
- 这就是我选择的三指针遍历+回溯法
- 在一开始计算s1,s2,s3字符串的长度,如果len1+len2不等于len3,则直接返回false【此条的修改来源:Leetcode测试用例】
代码:
bool isInterleave(char * s1, char * s2, char * s3){
int len1 = strlen(s1);
int len2 = strlen(s2);
int len3 = strlen(s3);
if(len1 + len2 != len3)
return false;
int p1=0,p2=0,p3=0;
int turn = 0;
//决定从哪个字符串开始对比
if(s1[0] == s3[0])
turn = 1;
else if(s2[0] == s3[0])
turn = 2;
else
return false;
while(1){
//对比s1和s3字符串
if(turn == 1){
//当s1和s3未遍历完,且指针所指字符相同,且和s2指针所指字符不同时,指针前移
while(s1[p1] != '\0' && s3[p3] != '\0' && s1[p1] == s3[p3] && s2[p2] != s3[p3]){
p1++;
p3++;
}
if(s1[p1] != s3[p3] && s2[p2] == s3[p3])
turn = 2; //切换字符串
else if(s1[p1]=='\0' && s2[p2]=='\0' && s3[p3]=='\0'){
return true;
}else if(s1[p1] == s3[p3] && s2[p2] == s3[p3]){
if(isInterleave(&s1[p1+1],&s2[p2],&s3[p3+1]))
return true; //尝试选择s1字符串
else if(isInterleave(&s1[p1],&s2[p2+1],&s3[p3+1]))
return true; //回溯,尝试选择s2字符串
else
return false; //都不行,无解,返回false
}else
return false;
}
else{
//对比s2和s3字符串,一切和上面同理
while(s2[p2] != '\0' && s3[p3] != '\0' && s2[p2] == s3[p3] && s1[p1] != s3[p3]){
p2++;
p3++;
}
if(s2[p2] != s3[p3] && s1[p1] == s3[p3])
turn = 1;
else if(s1[p1]=='\0' && s2[p2]=='\0' && s3[p3]=='\0'){
return true;
}else if(s1[p1] == s3[p3] && s2[p2] == s3[p3]){
if(isInterleave(&s1[p1+1],&s2[p2],&s3[p3+1]))
return true;
else if(isInterleave(&s1[p1],&s2[p2+1],&s3[p3+1]))
return true;
else
return false;
}else
return false;
}
}
}
可惜的是最终超过时间限制,败北
相同字符过于多,回溯发生过于频繁导致超时
官方优秀解法:动态规划
解法3.0:说到动态规划,就是经典的三步走,首先设计一个表示法,可以表示该题任意规模下的解;然后给出状态迁移公式,从而从小规模问题逐渐推出目标大规模问题的解;最后讨论一下边缘特殊值/初始值。
状态表示: f(i,j)表示s1前i个字符和s2前j个字符是否可以交错组成s3前i+j个字符(0≤i≤len1,0≤j≤len2)
状态迁移: 如果s3最后一位等于s1最后一位,f(i,j)=f(i-1,j);同理如果s3最后一位等于s2最后一位,f(i,j)=f(i,j-1)
因此
f
(
i
,
j
)
=
(
f
(
i
−
1
,
j
)
⋂
(
s
1
[
i
−
1
]
=
=
s
3
[
p
]
)
)
⋃
(
f
(
i
,
j
−
1
)
⋂
(
s
2
[
j
−
1
]
=
=
s
3
[
p
]
)
)
f(i,j)=(f(i-1,j)\bigcap (s1[i-1]==s3[p]))\bigcup (f(i,j-1)\bigcap (s2[j-1]==s3[p]))
f(i,j)=(f(i−1,j)⋂(s1[i−1]==s3[p]))⋃(f(i,j−1)⋂(s2[j−1]==s3[p]))
注:p=i+j-1
边界值: f(0,0)=true
仔细观察状态迁移公式可以发现,对于数据结构f(i,j)只会用到当前行i和上一行i-1,因此可以节省空间,使用滚动数组
状态迁移公式更改为:
f
(
j
)
=
(
f
(
j
)
⋂
(
s
1
[
i
−
1
]
=
=
s
3
[
p
]
)
)
⋃
(
f
(
j
−
1
)
⋂
(
s
2
[
j
−
1
]
=
=
s
3
[
p
]
)
)
f(j)=(f(j)\bigcap (s1[i-1]==s3[p]))\bigcup (f(j-1)\bigcap (s2[j-1]==s3[p]))
f(j)=(f(j)⋂(s1[i−1]==s3[p]))⋃(f(j−1)⋂(s2[j−1]==s3[p]))
具体看代码:
bool isInterleave(char * s1, char * s2, char * s3){
int len1 = strlen(s1);
int len2 = strlen(s2);
int len3 = strlen(s3);
if(len1 + len2 != len3)
return false;
int* f = (int *)malloc(sizeof(int) * (len2 + 1)); //滚动数组
for(int i=0;i<=len2;i++){
f[i] = false;
}
f[0] = true; //边缘值
for(int i=0;i<=len1;i++){
for(int j=0;j<=len2;j++){
int p = i+j-1;
if(i>0){
f[j] &= (s1[i-1] == s3[p]); //i>0
}
if(j>0){
f[j] |= f[j-1] && (s2[j-1] == s3[p]); //j>0
}
}
}
return f[len2];
}
本人菜鸡一只
如有错误,欢迎指正,卑微鞠躬