力扣:超出时间限制——不如试试动态规划

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(i1,j)(s1[i1]==s3[p]))(f(i,j1)(s2[j1]==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[i1]==s3[p]))(f(j1)(s2[j1]==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];
}

本人菜鸡一只
如有错误,欢迎指正,卑微鞠躬

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值